Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for OpenSCAD #2090

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions lib/rouge/demos/scad
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// --------------------------------------------------------------------
// a round groove to be substracted from other solids
// --------------------------------------------------------------------
module round_groove(length = goove_length)
{
groove_radius = 2.25;
groove_center = 2.4;
groove_width = 3.0;
union()
{
translate([0, groove_center, 0] )
cylinder(r = groove_radius, h=len + 2 * manifold_correction, center=true);
translate([0, 0.5, 0] )
cube([groove_width,1.5,len + 2 * manifold_correction], center = true);
}
}
106 changes: 106 additions & 0 deletions lib/rouge/lexers/scad.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# -*- coding: utf-8 -*- #
# frozen_string_literal: true

module Rouge
module Lexers
class Scad < Go
title "Scad"
desc 'The OpenSCAD 3D programming language (https://openscad.org/documentation.html#language-reference)'
tag 'scad'
aliases 'scad', 'openscad'
filenames '*.scad'

mimetypes 'text/x-scad'

# This lexer is inherited from the go lexer, which looks clear and conchise :-)
# Those definitions which are different for OpenSCAD are overwritten and the
# states are modified to add the required and remove the unused regexp

# Keywords related to OpenSCAD

KEYWORD = /\b(?:
function | module | if | else | let | for | intersection_for
)\b/x


# Operators and delimiters related to OpenSCAD

OPERATOR = / \+ | - | \*
| % | \^
| <= | < | >= | >
| != | == | = | \!
| && | \|\|
| \/ | \#
/x

SEPARATOR = / \( | \) | \[ | \] | \{
| \} | , | ; | \: | \?
/x

# Literals related to OpenSCAD
INT_LIT = /#{DECIMAL_LIT}/
ESCAPED_CHAR = /\\[nrt\\"]/
CHAR_LIT = /'(?:#{UNICODE_VALUE})'/

# Predeclared identifiers related to OpenSCAD

SPECIAL_VARIABLES = /\$(fa|fs|fn|t|vpr|vpt|vpd|vpf|children|preview|parent_modules)/

PREDECLARED_CONSTANTS = /\b(?:true|false|PI|undef)\b/

PREDECLARED_FUNCTIONS = /\b(?:
circle | square | polygon | text
| sphere | cube | cylinder | polyhedron
| translate | rotate | scale | resize
| mirror | multimatrix | color | offset
| hull | minkowski
| union | difference | intersection
| is_undef | is_bool | is_num | is_string
| is_list | is_function
| concat | lookup | str | chr
| ord | search | version | version_num
| parent_module
| abs | sign | sin | cos
| tan | acos | asin | atan
| atan2 | floor | round | ceil
| ln | len | let | log
| pow | sqrt | exp | rands
| min | max | norm | cross
| echo | render | surface | assert
)\b/x

state :root do
mixin :include_or_use
mixin :simple_tokens
rule(/"/, Str, :interpreted_string)
end

state :simple_tokens do
rule(COMMENT, Comment)
rule(KEYWORD, Keyword)
rule(SPECIAL_VARIABLES, Name::Variable)
rule(PREDECLARED_FUNCTIONS, Name::Builtin)
rule(PREDECLARED_CONSTANTS, Name::Constant)
rule(FLOAT_LIT, Num)
rule(INT_LIT, Num)
rule(CHAR_LIT, Str::Char)
rule(OPERATOR, Operator)
rule(SEPARATOR, Punctuation)
rule(IDENTIFIER, Name)
rule(WHITE_SPACE, Text)
end

# use or include are somehow special:
# Although they are keywords, they are valid only in combination with
# <subdir_and_filename> which uses the < and > which normally are operators
# Here they are used as punctuation and the path is highlighted as an
# ordinary string value even without the delimiting "".
state :include_or_use do
rule /^#{WHITE_SPACE}*(include|use)(#{WHITE_SPACE}+)(<)(.*)(>).*([\n])/ do
groups Keyword, Text, Punctuation, Str, Punctuation, Text
end
end

end
end
end
18 changes: 18 additions & 0 deletions spec/lexers/scad_spec
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# -*- coding: utf-8 -*- #
# frozen_string_literal: true

describe Rouge::Lexers::Scad do
let(:subject) { Rouge::Lexers::Scad.new }

describe 'guessing' do
include Support::Guessing

it 'guesses by filename' do
assert_guess :filename => 'foo.scad'
end

it 'guesses by mimetype' do
assert_guess :mimetype => 'text/x-scad'
end
end
end
199 changes: 199 additions & 0 deletions spec/visual/samples/scad
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
use <text/test.scad>
include <text/test.scad>

// --------------------------------------------------------------------
// A collection of useful modules to create Fischertechnik parts,
// mainly grooves, holes and pins to be added or substracted
// --------------------------------------------------------------------

basic_block_size = 15;
basic_block_half = basic_block_size / 2;
basic_block_center = basic_block_half;

goove_length = 15.0;
manifold_correction = 0.1;
eyelet_len = 2.5;

$fn = 50;

// --------------------------------------------------------------------
// a round groove to be substracted from other solids
// --------------------------------------------------------------------
module round_groove(length = goove_length)
{
groove_radius = 2.25;
groove_center = 2.4;
groove_width = 3.0;
union()
{
translate([0, groove_center, 0] )
cylinder(r = groove_radius, h=length + 2 * manifold_correction, center=true);
translate([0, 0.5, 0] )
cube([groove_width,1.5,length + 2 * manifold_correction], center = true);
}
}

// --------------------------------------------------------------------
// a flat groove to be substracted from other solids
// --------------------------------------------------------------------
module flat_groove(length = goove_length)
{
render() intersection()
{
round_groove(length);
translate([-2.3, -0.25, -(length + 2 * manifold_correction)/2] )
cube([4.6,3.05,length + 2 * manifold_correction]);
}
}


// Apply a size correction to the eyelets which are printed on
// the build plate of the 3D printer?
// On my Ender 3 the eyelets on the build plate are too tall so
// I needed to widen them. The eyelets on the vertical wall of the
// girder are ok, they need no correction.
function apply_correction(val, correctionVal = 0.0, correction=false) = correction == true ? val + correctionVal : val;


// --------------------------------------------------------------------
// a single eyelet for the girders and struts
// --------------------------------------------------------------------
module eyelet(height=eyelet_len,radius=2.05, correctionVal = 0.0, horizontal=false)
{
render() union()
{
radius = apply_correction(radius, correctionVal, horizontal);
cube_width = apply_correction(7.2, correctionVal, horizontal);
cube_height = apply_correction(3.1, correctionVal, horizontal);

translate([0, 0, horizontal == false ? -(height/2) + manifold_correction : (height/2) - manifold_correction ])
{
cylinder(h=height,r=radius, center=true);
cube([cube_height,cube_width,height], center=true);
}
}
}

// --------------------------------------------------------------------
// a row of eyelets
// --------------------------------------------------------------------
module eyelet_row(length = 1, height=eyelet_len, eyelet_dist = 15.0, correction = 0.0, horizontal = false)
{
cnt = length / eyelet_dist;
for (i = [0 : eyelet_dist : (cnt - 1) * eyelet_dist])
{
translate([eyelet_dist / 2, eyelet_dist / 2 + i ,0])
eyelet(height=height, horizontal=horizontal, correctionVal=correction);
}
}


// --------------------------------------------------------------------
// basic cube with cylinder to cut out holes for fixing the pins of the
// basic blocks. Used by cube_with_cylinder to build zero point centered
// variants for the four main directions.
// --------------------------------------------------------------------
module cwc(width = 4.2, s = 3.2, c = 0.8, h = 2.8)
{
r = width /2;
segment_height = r - sqrt(4 * pow(r,2) - pow(s,2))/2;
cylinder_center_offset = r - segment_height;

translate([0, cylinder_center_offset , width])
{
difference()
{
union()
{
translate([-width/2,-width/2 - manifold_correction,0])
cube([width,width,width]);
translate([0,-segment_height,0])
cube([s,s,width*2], center = true);
translate([0,c,0])
cylinder(h=width*2, r=width/2,center = true);
}
//#translate([-width/2 - manifold_correction /2, -width/2 + 2.8, 0])
translate([-width/2 - manifold_correction /2, -width/2 + h, 0])
cube([width + manifold_correction,width + manifold_correction,width + manifold_correction]);
}
}
}

// --------------------------------------------------------------------
// A cube with attached cylinder to cut out holes for the pins of the
// basic blocks. Centered on the zero point of the coordinate system.
// Supports the four directions up, down, left and right.
// The build plate must be face down in the coordinate system to cut
// out the holes with this module.
// if the holes are too tall, adjust width (the width and height of
// the larger square hole) or the segment width s (the width of the
// the smaller rectangle)
// --------------------------------------------------------------------
module cube_with_cylinder(width = 4.2, s = 3.2, co = 0.8, h = 2.8, dir = "up")
{

if (dir == "up")
{
translate([0,width,0])
rotate([90,0,0])
cwc(width = width, s = s, h = h, c = co);
}
else if (dir == "down")
{
translate([0,-width,0])
rotate([90,0,180])
cwc(width = width, s = s, h = h, c = co);
}
else if (dir == "left")
{
translate([width,0,0])
rotate([90,0,270])
cwc(width = width, s = s, h = h, c = co);
}
else if (dir == "right")
{
translate([-width,0,0])
rotate([90,0,90])
cwc(width = width, s = s, h = h, c = co);
}
}

// --------------------------------------------------------------------
// Pin which fits into the round and flat grooves.
// The base_length is for pins placed on blocks or plates
// --------------------------------------------------------------------
module pin(diameter = 4, base_width = 3, base_length = 1.2)
{
rotate([90,0,0])
translate([0, (base_width + base_length) / 2, 0])
union()
{
difference()
{
// build the rounded part of the pin
intersection()
{
cylinder(d = diameter, h = diameter, center = true);
rotate([0, 90, 0])
cylinder(d = diameter, h = diameter, center = true);
}
// cut off the top
translate([0, diameter / 2 + 0.6, 0])
cube([diameter, diameter, diameter], center = true );
}
// add the base below the rounded part
translate([0, -(base_width / 2), 0])
cube([base_width, base_length, base_width], center = true );
}
}

// --------------------------------------------------------------------
// Final test for some exotic features like the modifiers
// --------------------------------------------------------------------
module Test()
{
%rotate([90,0,0]) { }
#translate([0, 0, 0]) { }
!union() { }
*difference() { }
}