From: Ian Jackson Date: Sat, 16 Sep 2023 11:05:22 +0000 (+0100) Subject: Move a lot of files into the utils tree X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=reprap-play.git;a=commitdiff_plain;h=87a9c854ddebe081c77211aa828675afd3f99d99 Move a lot of files into the utils tree Signed-off-by: Ian Jackson --- diff --git a/commitid.scad.pl b/commitid.scad.pl new file mode 100755 index 0000000..62ae510 --- /dev/null +++ b/commitid.scad.pl @@ -0,0 +1,917 @@ +#!/usr/bin/perl -w + +# commitid.scad.pl - a program for annotating solid models with commit info +# Copyright (C)2016 Ian Jackson. See below. There is NO WARRANTY. + + +# USAGE +# ===== +# +# .../commitid.scad.pl [OPTION...] [STRING...] >commitid.scad.new \ +# && mv -f commitid.scad.new commitid.scad +# +# Run without arguments, commitid.scad.pl will output an openscad file +# which contains 2D and 3D models of the current git commit count and +# commit object id (commit hash), useful for identifying printed +# parts. +# +# See below for details. You probably want these two sections, as a +# quick starting point: +# General form of provided openscad modules +# Autoscaling modules +# +# We can also generate models of short mainly-numeric strings +# specified on the command line. +# +# +# Options: +# +# --git Generate git commit indications, as shown below +# (this is the default if no strings are requested with -t). +# Ie, produce the `Autoscaling modules' and `Specific layouts'. +# +# --git=objid +# Generate git commit indication based on commit object only +# (ie avoid counting commits). Ie, do not generate `Small' +# and `Full' layouts (and never select them for `Best'). +# +# -i Do not generate `+' dirty indication if git-untracked files +# are present (ie, missing .gitignore entries). The `*' +# dirty tree indication (for modified files) cannot be disabled. +# +# [-t[LAYOUT]] TEXT +# Generate a layout LAYOUT containing TEXT. TEXT can +# contain newlines (a final newline usually undesirable, as +# it will generate a blank line). If LAYOUT is not specified, +# generates Arg0, Arg1, Arg2, etc., for successive such +# TEXTs. The permissible character set in is TEXT is: +# space 0-9 a-f + * +# +# +# OPENSCAD INTERFACE +# ================== +# +# Dynamic variables for configuration +# ----------------------------------- +# +# We honour the following variables to control various scaling factors: +# +# default value notes +# $Commitid_pixelsz 0.8 \ multiplied together +# $Commitid_scale 1.0 / +# $Commitid_depth pixelsz/2 \ multiplied together +# $Commitid_depth_scale 1.0 / +# $Commitid_max_best_scale 2.0 limits XY scaling in *Best* +# +# FYI the font is nominally 3x5 pixels, with 1-pixel inter-line and +# inter-character gaps. (It's not strictly speaking a 3x5 bitmap +# font, size it contains partial pixels and diagonals.) +# +# +# Non-`module'-specific functions +# ------------------------------- +# +# We provide the following functions (which depend on the config +# variables, but not on anything else) and compute useful values: +# +# function Commitid_pixelsz() Actual size of each nominal pixel +# function Commitid_depth() Depth to use (the amount characters +# should be raised or sunken) +# +# General form of provided openscad modules +# ----------------------------------------- +# +# module Commitid_MODULE_2D(...) Collection of polygons forming characters +# module Commitid_MODULE(...) The above, extruded up and down in Z +# module Commitid_MODULE_M_2D(...) Mirror writing +# module Commitid_MODULE_M(...) 3D mirror writing +# function Commitid_MODULE_sz() A 2-vector giving the X,Y size +# +# Except for *Best* modules, the XY origin is in the bottom left +# corner without any margin. Likewise Commitid_MODULE_sz does not +# include any margin. +# +# For 3D versions, the model is 2*depth deep and the XY plane bisects +# the model. This means it's convenient to either add or subtract from +# a workpiece whose face is in the XY plane. +# +# The _M versions are provided to avoid doing inconvenient translation +# and rotation to get the flipped version in the right place. +# +# +# Autoscaling modules +# ------------------- +# +# These modules take a specification of the available XY space, and +# select and generate a suitable specific identification layout: +# +# module Commitid_BestCount_2D (max_sz, margin=Commitid_pixelsz()) +# module Commitid_BestCount (max_sz, margin=Commitid_pixelsz()) +# module Commitid_BestCount_M_2D(max_sz, margin=Commitid_pixelsz()) +# module Commitid_BestCount_M (max_sz, margin=Commitid_pixelsz()) +# module Commitid_BestObjid_2D (max_sz, margin=Commitid_pixelsz()) +# module Commitid_BestObjid (max_sz, margin=Commitid_pixelsz()) +# module Commitid_BestObjid_M_2D(max_sz, margin=Commitid_pixelsz()) +# module Commitid_BestObjid_M (max_sz, margin=Commitid_pixelsz()) +# +# max_sz should be [x,y]. +# +# BestCount includes (as much as it can of) the git commit count, +# ie the result of +# git rev-list --first-parent --count HEAD +# (and it may include some of the git revision ID too). +# +# BestObjid includes as much as it can of the git commit object hash, +# and never includes any of the count. +# +# All of these will autoscale and autorotate the selected model, and +# will include an internal margin of the specified size (by default, +# one pixel around each edge). If no margin is needed, pass margin=0. +# +# There are no `function Commitid_Best*_sz'. If they existed they +# would simply return max_sz. +# +# +# Output format +# ------------- +# +# In general the output, although it may be over multiple lines, +# is always in this order +# git commit object id (hash) +# dirty indicator +# git commit count +# +# Not all layouts have all these parts. The commit object id may +# sometimes be split over multiple lines, but the count will not be. +# If both commit id and commit count appear they will be separated +# by (at least) a newline, or a dirty indicator, or a space. +# +# The commit id is truncated to fit, from the right. +# +# The commit count is truncated from the _left_, leaving the least +# significant decimal digits. +# +# The dirty indicator can be +# +# * meaning the working tree contains differences from HEAD +# +# + meaning the working tree contains untracked files +# (ie files you have failed to `git add' and also failed +# to add to gitignore). (But see the -i option.) +# +# +# Specific layouts +# ---------------- +# +# If you want to control the exact layout (and make space for it in +# your design), you can use these: +# +# module Commitid_LAYOUT_2D() +# module Commitid_LAYOUT() +# module Commitid_LAYOUT_M_2D() +# module Commitid_LAYOUT_M() +# function Commitid_LAYOUT_sz() +# +# Here LAYOUT is one of the following (giving for example, `module +# Commitid_Full8_2D'). In the examples, we will assume that the tree +# is dirty, the commit count is 123456, and the commit object id +# starts abcdeffedbcaabcdef... In the examples `_' shows where a +# space would be printed. +# +# Small2 Small3 ... Small9 Small10 Small12 Small14 Small16 +# A single line containing as much of the count will fit, eg: +# Small5 3456* +# Small8 _*123456 +# The objectid is included if more than one character of of it +# will fit without makign the output ambiguous: +# Small9 ab*123456 +# +# Small2S Small4S ... Small16S +# Small3T Small9T Small12T +# Same as Small but split into two lines (S) +# or three lines (T). Eg: +# Small4S *4 Small6T _* +# 56 34 +# 56 +# Git2 Git3 ... Git9 Git10 Git12 Git14 Git16 +# Git4S Git6S ... Git16S +# Git6T Git9T Git12T +# Just the commit object hash, in one, two (S) or three (T) +# lines. E.g.: +# Git5 abcd* +# +# Full4 Full6 ... Full20: +# The commit object hash plus the commit count, on +# separate lines, eg: +# Full12 abcdef Full16 abcdeffe +# *23456 _*123456 +# +# Full6T Full9T ... Full30T +# As Full but the commit object id is split over two lines +# producing a 3-line layout, eg: +# Full9T abc Full21T abcdeff +# de* edbcaa* +# 456 _123456 +# +# Other LAYOUTs +# ------------- +# +# FontDemo +# +# A demonstration of the built-in 18-character font +# +# Arg0 Arg1, ... +# +# Strings passed on command line (without -t, or bare -t, +# rather than with -tLAYOUT). +# +# LAYOUT +# +# Generated by passing -tLAYOUT on the command line. +# + + +# COPYRIGHT, LICENCE AND LACK-OF-WARRANTY INFORMATION +# =================================================== +# +# This program is Free Software and a Free Cultural Work. +# +# You can redistribute it and/or modify it under the terms of the +# GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# Alternatively, at your option: +# +# This work is licensed under the Creative Commons +# Attribution-ShareAlike 4.0 International License. +# +# There is NO WARRANTY. + + +use strict; + +$SIG{__WARN__} = sub { die @_; }; + +our $debug=0; + +if (@ARGV && $ARGV[0] =~ m/^-(D+)$/) { + $debug = length $1; + shift @ARGV; +} + +sub p { print @_ or die $!; } + +sub p_debug { print STDERR @_ if $debug; } + +p <<'END'; +// *** AUTOGENERATED - DO NOT EDIT *** // +function Commitid_pixelsz() = + ($Commitid_pixelsz ? $Commitid_pixelsz : 0.8) * + ($Commitid_scale ? $Commitid_scale : 1.0); +function Commitid_depth() = + ($Commitid_depth ? $Commitid_depth : Commitid_pixelsz()/2) * + ($Commitid_depth_scale ? $Commitid_depth_scale : 1.0); +function Commitid__scale() = + Commitid_pixelsz() / 0.2; +END + +sub chrmodname ($) { + my ($chr) = @_; + my $chrx = sprintf '%#x', ord $chr; + return "Commitid__chr_$chrx"; +} + +our $gtm_demo_i = -1; +our $gtm_demo_j; +our @gtm_demo_o; + +sub gentextmodule_demo_start_batch () { + $gtm_demo_j = 0; + $gtm_demo_i++; +} + +sub argl_formal (@) { join ', ', @_; } +sub argl_actual (@) { join ',', map { m/=/ ? $` : $_ } @_; } + +sub gen3dmodule ($@) { + my ($modb,$size,@argl) = (@_); + $size ||= "${modb}_sz()"; + p "module ${modb}_M_2D(".argl_formal(@argl)."){\n"; + p " translate([${size}[0],0])\n"; + p " mirror([1,0,0])\n"; + p " ${modb}_2D(".argl_actual(@argl).");\n"; + p "};\n"; + foreach my $mir ('','_M') { + my $mm = "${modb}${mir}"; + p "module ${mm}(".argl_formal(@argl)."){\n"; + p " d=Commitid_depth();\n"; + p " translate([0,0,-d]) linear_extrude(height=d*2)\n"; + p " ${mm}_2D(".argl_actual(@argl).");\n"; + p "}\n"; + } +} + +sub gentextmodule ($@) { + my ($form, @lines) = @_; + my $modb = "Commitid_$form"; + p "module ${modb}_2D(){\n"; + p " // |$_|\n" foreach @lines; + p " scale(Commitid__scale()){\n"; + my $y = @lines; + my $cols = 1; + foreach my $line (@lines) { + $y--; + my $x = 0; + foreach my $chr (split //, $line) { + p sprintf " translate([%d * 0.8, %d * 1.2]) %s();\n", + $x, $y, chrmodname $chr + if $chr =~ m/\S/; + $x++; + } + $cols = $x if $x > $cols; + } + p " }\n"; + p "}\n"; + gen3dmodule($modb,''); + + p sprintf "function %s_sz() = Commitid__scale() * 0.1 * [ %d, %d ];\n", + $modb, 2 * ($cols * 4 - 1), 2 * (@lines * 6 - 1); + + push @gtm_demo_o, <commitid-DEBUG-simplify-$chr.ps"; + print S "%!\n"; + print S "(Courier-Bold) findfont 15 scalefont setfont\n"; + + $prcount=0; +} + +sub debug_simplify_done () { + return unless $debug; + print S "showpage\n"; + close S or die $!; +} + +sub debug_simplify_pr ($$$) { + my ($chr,$polys,$why) = @_; + + return unless $debug; + + print STDERR "PR $chr $why\n"; + my $ct_x = 10000 * ($prcount % 6); + my $ct_y = 18000 * int($prcount / 6); + printf S "0 setgray\n"; + printf S "%d %d moveto\n", map {$_/100 + 10} $ct_x,$ct_y; + printf S "(%s) show\n", $why; + my $pr_recur; + + $pr_recur = sub { + my ($tpolys, @levels) = @_; + return unless @$tpolys; + foreach my $i (0..$#$tpolys) { + printf STDERR "P@levels %02d :", $i; + my $pinfo = $tpolys->[$i]; + my $p = $pinfo->{E}; + printf STDERR "@$p\n"; + my $lw = 5 - 4*($i / ($#$tpolys || 1)); + my $pp = sub { + my $spec = $p->[$_[0]]; + $spec =~ m/^\d{5}/; + sprintf "%d %d",map { $_/100 } + 1000 + $ct_x + $&, + 5000 + $ct_y + $'; + }; + printf S "%s setrgbcolor\n", (@levels==0 ? '0 0 0' : + @levels==1 ? '0 0 1' + : '1 1 0'); + foreach my $eai (0..$#$p) { + my $ebi = ($eai + 1) % @$p; + printf S <($eai), $pp->($ebi); + %f setlinewidth + %s moveto + %s lineto + stroke +END + } + $pr_recur->($pinfo->{Holes}, @levels, $i); + } + }; + $pr_recur->($polys,0); + + $prcount++; +} + +sub simplify ($$) { + my ($chr,$polys) = @_; + use Data::Dumper; + + return unless @$polys; + + my $count=0; + my $pr = sub { }; + + if ($debug) { + debug_simplify_begin($chr); + } + + $pr->("start"); + + AGAIN: while(1) { + my %edges; + my $found_hole; + + foreach my $pi (0..$#$polys) { + my $p = $polys->[$pi]{E}; + foreach my $ei (0..$#$p) { + my $e = $p->[$ei].$p->[($ei+1) % @$p]; + die if $edges{$e}; + $edges{$e} = [ $p, $pi, $ei ]; + } + } + p_debug "AGAIN $count\n"; + my $merge = sub { + my ($pa, $pai, $eai, $pb, $pbi, $ebi) = @_; + p_debug "# merging $pai:$eai.. $pbi:$ebi..\n"; + splice @$pa, $eai, 1, + ((@$pb)[$ebi+1..$#$pb], (@$pb)[0..$ebi-1]); + @$pb = ( ); + }; + foreach my $pai (0..$#$polys) { + my $painfo = $polys->[$pai]; + my $pa = $painfo->{E}; + foreach my $eai (0..$#$pa) { + my $ear = $pa->[ ($eai+1) % @$pa ].$pa->[$eai]; + my $ebi = $edges{$ear}; + next unless $ebi; + my ($pb,$pbi); + ($pb, $pbi, $ebi) = @$ebi; + # $pai:($eai+1)..$eai and $pbi:$ebi..($ebi+1) are identical + # so we want to remove them. + if ($pai==$pbi) { + # we're making a hole! we make an assumption: + # holes have fewer line segments than the + # outlines. This is almost always true because of + # the way we construct our figures. + if (($ebi - $eai + @$pa) % @$pa > @$pa/2) { + # We arrange that $eai..$ebi is the hole + ($ebi,$eai) = ($eai,$ebi); + } + p_debug "HOLE $eai $ebi\n"; + # we want to make the smallest hole, to avoid + # making a hole that itself needs simplifying + my $holesz = ($ebi - $eai + @$pa) % @$pa; + $found_hole = [ $pa,$pai,$eai, $ebi, $holesz ] + unless $found_hole && $found_hole->[4] < $holesz; + } else { + $merge->($pa,$pai,$eai,$pb,$pbi,$ebi); + debug_simplify_pr($chr,$polys,"after $count"); + next AGAIN; + } + } + # we process hole joining last, so that the whole of the + # edge of the hole must be part of the same polygon + if ($found_hole) { + p_debug "HOLE DOING @$found_hole\n"; + my ($pa,$pai,$eai,$ebi) = @$found_hole; + # simplify the indexing + @$pa = ((@$pa)[$eai..$#$pa], (@$pa)[0..$eai-1]); + $ebi -= $eai; $ebi += @$pa; $ebi %= @$pa; + $eai = 0; + push @{ $painfo->{Holes} }, { + E => [ (@$pa)[$eai+1..$ebi-1] ], + Holes => [ ], + }; + splice @$pa, $eai, $ebi-$eai+1; + debug_simplify_pr($chr,$polys,"hole $count"); + next AGAIN; + } + } + last; + } + + debug_simplify_done(); +} + +sub p_edgelist ($$$) { + my ($points,$vecs,$p) = @_; + my @vec; + foreach my $pt (@$p) { + $pt =~ s{\d{5}}{$&,}; + $pt =~ s{\b\d}{$&.}g; + push @$points, "[$pt]"; + push @vec, $#$points; + } + push @$vecs, \@vec; +} + +sub parsefont () { + my %cellmap; + for (;;) { + $_ = // die; + last if %cellmap && !m/\S/; + next unless m/\S/; + chomp; + s{^(.) }{}; + $cellmap{$1} = $_; + } + my %chrpolys; + # $chrs{$chr}[$poly] = $poly + # $poly->{E} = [ "012345012345", ... ] + # $poly->{Holes} = $poly2 + while () { + next unless m/\S/; + chomp; + my @chrs = split / /, $_; + !~ m/\S/ or die; + foreach my $row (reverse 0..4) { + $_ = ; + chomp; + s{^}{ }; + $_ .= ' ' x 8; + m{\S/\S} and die; + s{/(?=\s)}{L}g; + s{/(?=\S)}{r}g; + s{\\(?=\s)}{l}g; + s{\\(?=\S)}{R}g; + p "// $_\n"; + foreach my $chr (@chrs) { + s{^ }{} or die "$chr $_ ?"; + foreach my $col (0..2) { + my @verts; + if (s{^ }{}) { + } elsif (s{^\S}{}) { + my $f = $cellmap{$&}; + die unless $f; + $f =~ s/\b\d/ sprintf '%05d', $col*2000 + $&*1000 /ge; + $f =~ s/\d\b/ sprintf '%05d', $row*2000 + $&*1000 /ge; + push @{ $chrpolys{$chr} }, { E => [ split / /, $f ] }; + } else { + die "$_ ?"; + } + } + } + die "$_ ?" if m{\S}; + } + } + + my $demo = ''; + my $democols = 6; + foreach my $chr (sort keys %chrpolys) { + + my $polys = $chrpolys{$chr}; + $_->{Holes} = [] foreach @$polys; + + simplify($chr,$polys); + + my $mod = chrmodname $chr; + p "module $mod () {\n"; + foreach my $poly (@$polys) { + p " polygon("; + my $holes = $poly->{Holes}; + my (@points, @vecs); + p_edgelist(\@points, \@vecs, $poly->{E}); + foreach my $hole (@$holes) { + p_edgelist(\@points, \@vecs, $hole->{E}); + } + p "points=[".(join ",",@points)."],"; + if (@$holes) { + p ",paths=[".(join ",", + map { "[".(join ",",@$_)."]" } + @vecs)."],"; + } + p "convexity=4);\n"; + } + p "}\n"; + $demo .= $chr; + } + @demo = reverse $demo =~ m{.{1,$democols}}go; +} + +parsefont(); + +our $do_git; # contains may chars 'c' (count) and/or 'o' (object) +our $do_git_untracked = 1; +our $argcounter; + +our @forms; +our %included; # 0 = not at all; 1 = truncated; 2 = full + +sub rjustt ($$$;$) { + # right justify and truncate (ie, pad and truncate at left) + # always includes prefix + # sets $included{$what} + my ($sz, $what, $whole, $prefix) = @_; + $prefix //= ''; + my $lw = length $whole; + my $spare = $sz - $lw - (length $prefix); + $included{$what}= 1 + ($spare > 0); + return + ($spare > 0 ? (' ' x $spare) : ''). + $prefix. + substr($whole, ($spare < 0 ? -$spare : 0)); +} + +sub ljustt ($$$;$) { + my ($sz, $what, $whole, $suffix) = @_; + $suffix //= ''; + $sz -= length $suffix; + $included{$what} = 1 + ($sz >= length $whole); + return sprintf "%-${sz}.${sz}s%s", $whole, $suffix; +} + +sub genform_prep() { + $included{$_}=0 foreach qw(Objid Count); +} + +sub genform ($@) { + my ($form, @lines) = @_; + gentextmodule($form, @lines); + my $f = { + Form => $form, + Chars => (length join '', @lines), + Lines => (scalar @lines), + Ambiguous => ($form =~ m/Full/ && !grep { m/\W/ } @lines), + Included => { %included }, + }; + push @forms, $f; +} + +sub genform_q ($$$) { + my ($form, $s, $lines) = @_; + $gtm_demo_j++; + my $l = length $s; + return if $l % $lines; + my $e = $l/$lines; + return if $e < 2; + $gtm_demo_j--; + genform($form, $s =~ m/.{$e}/g); +} + +sub genform_plusq ($$) { + my ($form, $s) = @_; + genform($form, $s); + genform_q("${form}S", $s, 2); + genform_q("${form}T", $s, 3); +} + +our @gcmd; + +sub gitrun_start () { + open F, "-|", @gcmd or die "$gcmd[0]: start: $!"; +} + +sub gitrun_done (;$) { + my ($errok) = @_; + $?=0; $!=0; + return if close F; + return if $errok; + die $! if $!; + die "@gcmd failed ($?)\n"; +} + +sub gitoutput (@) { + (@gcmd) = (qw(git), @_); + gitrun_start; + $_ = ; + gitrun_done; + defined or die "@gcmd produced no output"; + chomp or die "@gcmd produced no final newline"; + $_; +} + +sub do_git () { + return unless $do_git; + + @gcmd = qw(git status --porcelain); + push @gcmd, qw(--untracked=no) unless $do_git_untracked; + + my $git_dirty = ''; + gitrun_start; + while () { + if (m/^\?\?/ && $do_git_untracked) { + $git_dirty = '+'; + next; + } + $git_dirty = '*'; + last; + } + gitrun_done($git_dirty eq '*'); + + my $git_count; + my $git_object; + + if ($do_git =~ m/c/) { + $git_count = gitoutput qw(rev-list --first-parent --count HEAD); + } + if ($do_git =~ m/o/) { + $git_object = gitoutput qw(rev-parse HEAD); + } + print STDERR join ' ', map { $_ // '?' } + "-- commitid", $git_object, $git_dirty, $git_count, "--\n"; + + foreach my $sz (2..10, qw(12 14 16)) { + gentextmodule_demo_start_batch(); + + if (defined($git_count)) { + genform_prep(); + my $smallstr = rjustt($sz, 'Count', $git_count, $git_dirty); + my $forgitobj = $sz - length($git_count) - 1; + if (defined($git_object) && $forgitobj >= 2) { + $smallstr = ljustt($forgitobj, 'Objid', $git_object). + ($git_dirty || ' '). + $git_count; + } + genform_plusq("Small$sz", $smallstr); + } + + genform_prep(); + genform_plusq("Git$sz", ljustt($sz, 'Objid', $git_object, $git_dirty)) + if defined $git_object; + + if (defined $git_count && defined $git_object && $sz<=10) { + genform_prep(); + genform("Full".($sz*2), + ljustt($sz, 'Objid', $git_object), + rjustt($sz, 'Count', $git_count, $git_dirty)); + + genform_prep(); + my $e = $sz; + genform("Full".($e*3)."T", + ljustt($e*2, 'Objid', $git_object, $git_dirty) + =~ m/.{$e}/g, + rjustt($e, 'Count', $git_count)); + } + } +} + +sub do_some_best ($$) { + my ($bestwhat, $formre) = @_; + my $modname = "Best$bestwhat"; + my $fullmodname = "Commitid_${modname}_2D"; + my @argl = qw(max_sz margin=Commitid_pixelsz()); + p "module $fullmodname(".argl_formal(@argl).") {\n"; + my $mbs = '$Commitid_max_best_scale'; + p " sc_max = $mbs ? $mbs : 2;\n"; + p " sz = max_sz - 2*[margin,margin];\n"; + my @do; + foreach my $f ( + sort { + $b->{Included}{$bestwhat} <=> $a->{Included}{$bestwhat} or + $b->{Chars} <=> $a->{Chars} or + $a->{Lines} <=> $b->{Chars} + } + grep { + $_->{Form} =~ m/$formre/ && + !$_->{Ambiguous} + } + @forms + ) { + my $form = $f->{Form}; + p " sz_$form = Commitid_${form}_sz();\n"; + foreach my $rot (qw(0 1)) { + my $id = "${form}_r${rot}"; + p " sc_$id = min(sc_max"; + foreach my $xy (qw(0 1)) { + p ",sz[$xy]/sz_$form","[",(($xy xor $rot)+0),"]"; + } + p ");\n"; + push @do, " if (sc_$id >= 1.0"; + push @do, " && sc_$id >= sc_${form}_r1" if !$rot; + push @do, ") {\n"; + push @do, " translate([margin,margin]) scale(sc_$id)\n"; + push @do, " rotate(90) translate([0,-sz_$form"."[1]])\n" if $rot; + push @do, " Commitid_${form}_2D();\n"; + push @do, " } else"; + } + } + push @do, < 00 20 22 02 11 +< 00 20 11 22 02 + +0 1 2 3 4 5 6 7 8 9 + +/#\ r /#\ ##\ # # ### /#/ ### /#\ /#\ +# # /# # # # # # # # # # # # +# # # /#/ ##< \## ##\ ##\ // >#< \## +# # # # # # # # # # # # # +\#/ /#\ ### ##/ # ##/ \#/ # \#/ ##/ + +a b c d e f + + # # /## + # /## # /#\ # +/## ##\ # /## #r# ### +# # # # # # # #/ # +\## ##/ \## \## \#/ # + ++ * + + # # + # \#/ +### ### + # /#\ + # # diff --git a/funcs.scad.cpp b/funcs.scad.cpp new file mode 100644 index 0000000..c34d157 --- /dev/null +++ b/funcs.scad.cpp @@ -0,0 +1,86 @@ +// -*- C -*- + +function vecdiff2d(a,b) = [b[0]-a[0], b[1]-a[1]]; +function vecdiff(a,b) = [b[0]-a[0], b[1]-a[1], b[2]-a[2]]; + +#define dsq(i) (a[i]-b[i])*(a[i]-b[i]) +function dist2d(a,b) = sqrt(dsq(0) + dsq(1)); +function dist(a,b) = sqrt(dsq(0) + dsq(1) + dsq(2)); +#undef dsq + +#define vsq(i) (v[i]*v[i]) +function vectorlen2d(v) = sqrt(vsq(0) + vsq(1)); +function vectorlen(v) = sqrt(vsq(0) + vsq(1) + vsq(2)); +#undef vsq + +function unitvector2d(v) = v / vectorlen2d(v); +function unitvector(v) = v / vectorlen(v); + +function atan2vector(v) = atan2(v[1], v[0]); + +// | m[0][0] m[0][1] | +// | m[1][0] m[1][1] | +function determinant2(m) = (m[0][0] * m[1][1] - m[0][1] * m[1][0]); + +function clockwise2d(v) = [v[1], -v[0]]; + +function rotate2d(theta, v) = [ v[0] * cos(theta) - v[1] * sin(theta), + v[1] * cos(theta) + v[0] * sin(theta) ]; + +// intersection of lines p1..p2 and p3..p4 +function line_intersection_2d(p1,p2,p3,p4) = [ + // from https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line + #define XY12 determinant2([p1,p2]) + #define XY34 determinant2([p3,p4]) + #define XU12 QU(0,1,2) + #define XU34 QU(0,3,4) + #define YU12 QU(1,1,2) + #define YU34 QU(1,3,4) + #define QU(c,i,j) determinant2([[ p##i[c], 1 ], \ + [ p##j[c], 1 ]]) + #define DENOM \ + determinant2([[ XU12, YU12 ], \ + [ XU34, YU34 ]]) + //---- + determinant2([[ XY12, XU12 ], + [ XY34, XU34 ]]) / DENOM, + determinant2([[ XY12, YU12 ], + [ XY34, YU34 ]]) / DENOM, + ]; + #undef XY12 + #undef XY34 + #undef XU12 + #undef XU34 + #undef YU12 + #undef YU34 + #undef QU + #undef DENOM + + +function circle_point(c, r, alpha) = [ c[0] + r * cos(alpha), + c[1] + r * sin(alpha) ]; + +#define d (dist2d(a,c)) +#define alpha (atan2(a[1]-c[1],a[0]-c[0])) +#define gamma (asin(r / d)) +#define beta (alpha + 90 - gamma) + +function tangent_intersect_beta(c,r,a) = + beta; + +function tangent_intersect_b(c,r,a) = + circle_point(c, r, beta); +#undef d +#undef alpha +#undef gamma +#undef beta + +function tangents_intersect_beta(cbig,rbig,csmall,rsmall) = + tangent_intersect_beta(cbig,rbig-rsmall,csmall); + +function reflect_in_y(p) = [-p[0], p[1]]; + +function angle_map_range(in,base) = + in < base ? in + 360 : + in >= base + 360 ? in - 360 : + in; diff --git a/reprap-objects.make b/reprap-objects.make new file mode 100644 index 0000000..33e92d6 --- /dev/null +++ b/reprap-objects.make @@ -0,0 +1,86 @@ +# reprap-objects Makefile, reuseable parts +# +# Build scripts for various 3D designs +# Copyright 2012-2016 Ian Jackson +# +# This work is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This work is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this work. If not, see . + + +HRR=/home/reprap +SLIC3R=$(HRR)/Slic3r/bin/slic3r +M4=m4 +SKEINFORGE=python $(HRR)/reprappro-software.git/skeinforge/skeinforge_application/skeinforge_utilities/skeinforge_craft.py + +CWD := $(shell pwd) +PLAY ?= $(CWD) + +AUTO_TOPLEVELS := $(foreach m,$(USING_AUTOS),$(shell $(PLAY)/toplevel-find $m)) + +AUTO_INCS += funcs.scad + +default: autoincs scads + +$(shell set -xe; $(PLAY)/commitid.scad.pl >commitid.scad.tmp; cmp commitid.scad.tmp commitid.scad || mv -f commitid.scad.tmp commitid.scad ) + +autoincs: $(AUTO_INCS) $(AUTO_STLS_INCS) +scads: $(addsuffix .auto.scad, $(AUTO_TOPLEVELS)) +stls: $(addsuffix .auto.stl, $(AUTO_TOPLEVELS)) + +%.auto.scads: %.scad + $(MAKE) $(addsuffix .auto.scad, $(shell $(PLAY)/toplevel-find $*)) +%.auto.stls: + $(MAKE) $(addsuffix .auto.stl, $(shell $(PLAY)/toplevel-find $*)) + +-include .*.d + +%.stl: %.scad $(AUTO_INCS) + openscad -d .$@.d.tmp -o $*.tmp.stl $< + @rm -f $@ + @sed -e 's/\.tmp\.stl:/.stl:/' <.$@.d.tmp >.$@.d + @rm .$@.d.tmp + mv -f $*.tmp.stl $@ + +AUTOBASE=$(shell echo $(1) | perl -pe 's/,\w+\.auto$$//') + +%: %.cpp + cpp -nostdinc -P <$< >$@.tmp && mv -f $@.tmp $@ + +funcs.scad: + +#%.gcode: %.stl +# $(SKEINFORGE) $< + +%.gcode: manual-gcode-generator %.m-g + $(PLAY)/$^ >$@.tmp && mv -f $@.tmp $@ + +%.dxf: %.eps + pstoedit -dt -f "dxf: -polyaslines -mm" $< $@ + +%: %.pl + ./$< >$@.tmp && mv -f $@.tmp $@ + +%: %.m4 + $(M4) -P >$@.tmp $< && mv -f $@.tmp $@ + +.PRECIOUS: %.auto.scad +%.auto.scad: $(PLAY)/toplevel-make Makefile $(PLAY)/toplevel-find + @echo ' write $@' + $< $@ >$@.tmp + @mv -f $@.tmp $@ + +.PRECIOUS: %.stl %.gcode %.eps %.dxf + +clean: + rm -f *~ *.stl *.auto.scad *.gcode .*.d $(AUTO_INCS) + diff --git a/toplevel-find b/toplevel-find new file mode 100755 index 0000000..c9174c9 --- /dev/null +++ b/toplevel-find @@ -0,0 +1,28 @@ +#!/usr/bin/perl -w +use strict; +use IO::File; + +@ARGV==1 or die; +my $base = $ARGV[0]; +$base =~ m/^\-/ and die; + +sub read_file ($); +sub read_file ($) { + my ($fn) = @_; + my $f = new IO::File "$fn", '<' or die "$fn $!"; + while (<$f>) { + if (m#^//// toplevels-from:#) { + defined($_ = <$f>) or die $!; + m#^include\s+\<(\S+)>\s*$# or die; + read_file($1); + next; + } + next unless m#^\s*(?:////\s?)?module\s+(\w+)\b.*////toplevel\b#; + print "$base,$1\n" or die $!; + } + $f->error and die "$fn $!"; +} + +read_file("$base.scad"); +close STDOUT or die $!; + diff --git a/toplevel-make b/toplevel-make new file mode 100755 index 0000000..2df668b --- /dev/null +++ b/toplevel-make @@ -0,0 +1,10 @@ +#!/usr/bin/perl -w +use strict; +die unless @ARGV==1; +die unless $ARGV[0] =~ m/^(.+),(\w+)(?:\..*)?/; +my ($base,$obj) = ($1,$2); +print < +$obj(); +END +close STDOUT or die $!; diff --git a/utils.scad b/utils.scad new file mode 100644 index 0000000..e440b5f --- /dev/null +++ b/utils.scad @@ -0,0 +1,51 @@ +// -*- C -*- + +// suitable for masking things within radius sqrt(2) only +module FArcSegment_mask(beta) { + for (i=[0 : 0.75 : 3]) { + rotate(i*beta/4) + polygon([[0, 0], + [1, 0], + [cos(beta/4), sin(beta/4)]]); + } +} + +module FArcSegment(xc,yc,inrad,outrad,alpha,delta) { + translate([xc,yc]) { + intersection() { + difference() { + circle(r=outrad, $fn=70); + circle(r=inrad, $fn=70); + } + rotate(alpha) scale(outrad*2) { + FArcSegment_mask(delta); + } + } + } +} + +module rectfromto(a,b) { + ab = b - a; + translate([min(a[0], b[0]), min(a[1], b[1])]) + square([abs(ab[0]), abs(ab[1])]); +} +module circleat(c,r) { translate(c) circle(r); } +module linextr(z0,z1, convexity=20) { + translate([0,0,z0]) + linear_extrude(height=z1-z0, convexity=convexity) + children(); +} + +module linextr_x_yz(x0,x1, convexity=20) { // XY turn into YZ + rotate([90,0,0]) + rotate([0,90,0]) + linextr(x0,x1, convexity=convexity) + children(); +} + +module linextr_y_xz(y0,y1, convexity=20) { // XY turn into YZ + rotate([0,0,180]) + rotate([90,0,0]) + linextr(y0,y1, convexity=convexity) + children(); +}