diff --git a/src/plugins/tools/view_25d/lay_plugin/built-in-macros/_d25_engine.rb b/src/plugins/tools/view_25d/lay_plugin/built-in-macros/_d25_engine.rb
new file mode 100644
index 000000000..591ea4085
--- /dev/null
+++ b/src/plugins/tools/view_25d/lay_plugin/built-in-macros/_d25_engine.rb
@@ -0,0 +1,249 @@
+# $autorun-early
+# $priority: 1
+
+require 'pathname'
+
+module D25
+
+ class D25ZInfo
+
+ attr_accessor :layer, :zstart, :zstop, :display
+
+ def initialize(_layer, _zstart, _zstop, _display)
+ self.layer = _layer
+ self.zstart = _zstart
+ self.zstop = _zstop
+ self.display = _display
+ end
+
+ end
+
+ class D25Display
+
+ attr_accessor :fill, :frame, :like
+
+ def initialize
+ self.fill = nil
+ self.frame = nil
+ self.like = nil
+ end
+
+ end
+
+ # The D25 engine
+
+ class D25Engine < DRC::DRCEngine
+
+ def initialize
+
+ super
+
+ @current_z = 0.0
+ @zstack = []
+
+ # clip to layout view
+ if RBA::LayoutView::current
+ self.clip(RBA::LayoutView::current.box)
+ end
+
+ end
+
+ def z(*args)
+
+ self._context("z") do
+
+ layer = nil
+ zstart = nil
+ zstop = nil
+ height = nil
+
+ args.each do |a|
+
+ if a.is_a?(Range)
+
+ zstart = a.min
+ zstop = a.max
+
+ elsif a.is_a?(DRC::DRCLayer)
+
+ if layer
+ raise("Duplicate layer argument")
+ end
+ layer = a
+
+ elsif a.is_a?(1.class) || a.is_a?(1.0.class)
+
+ if height
+ raise("Duplicate height specification")
+ end
+ height = a
+
+ elsif a.is_a?(Hash)
+
+ if a[:height]
+ if height
+ raise("Duplicate height specification")
+ end
+ height = a[:height]
+ end
+ if a[:zstart]
+ if zstart
+ raise("Duplicate zstart specification")
+ end
+ zstart = a[:zstart]
+ end
+ if a[:zstop]
+ if zstop
+ raise("Duplicate zstop specification")
+ end
+ zstop = a[:zstop]
+ end
+ invalid_keys = a.keys.select { |k| ![ :height, :zstart, :zstop ].member?(k) }
+ if invalid_keys.size > 0
+ raise("Keyword argument(s) not understood: #{invalid_keys.collect(&:to_s).join(',')}")
+ end
+
+ else
+ raise("Argument not understood: #{a.inspect}")
+ end
+
+ end
+
+ if ! zstart
+ zstart = @current_z
+ end
+ if ! zstop && ! height
+ raise("Either height or zstop must be specified")
+ elsif zstop && height
+ raise("Either height or zstop must be specified, not both")
+ end
+ if height
+ zstop = zstart + height
+ end
+ @current_z = zstop
+
+ if ! layer
+ raise("No layer specified")
+ end
+
+ info = D25ZInfo::new(layer.data, zstart, zstop, @display || D25Display::new)
+ @zstack << info
+
+ return info
+
+ end
+
+ end
+
+ def display(*args, &block)
+
+ display = D25Display::new
+
+ args.each do |a|
+
+ if a.is_a?(D25ZInfo)
+
+ @zstack.each do |z|
+ if z == a
+ z.display = display
+ end
+ end
+
+ elsif a.is_a?(Hash)
+
+ hollow_fill = 0xffffffff
+
+ if a[:color]
+ if !a[:color].is_a?(0xffffff.class)
+ raise("'color' must be a color value (an integer)")
+ end
+ display.fill = a[:color]
+ display.frame = a[:color]
+ end
+ if a[:frame]
+ if !a[:frame].is_a?(0xffffff.class)
+ raise("'frame' must be a color value (an integer)")
+ end
+ display.frame = a[:frame]
+ end
+ if a[:fill]
+ if !a[:fill].is_a?(0xffffff.class)
+ raise("'fill' must be a color value (an integer)")
+ end
+ display.fill = a[:fill]
+ end
+ if a[:hollow]
+ if a[:hollow]
+ display.fill = hollow_fill
+ end
+ end
+
+ if a[:like]
+ li = nil
+ if a[:like].is_a?(String)
+ li = RBA::LayerInfo::from_string(a[:like])
+ elsif a[:like].is_a?(RBA::LayerInfo)
+ li = a[:like]
+ else
+ raise("'like' must be a string or LayerInfo object")
+ end
+ display.like = li
+ end
+
+ invalid_keys = a.keys.select { |k| ![ :fill, :frame, :color, :hollow, :like ].member?(k) }
+ if invalid_keys.size > 0
+ raise("Keyword argument(s) not understood: #{invalid_keys.collect(&:to_s).join(',')}")
+ end
+
+ else
+ raise("Argument not understood: #{a.inspect}")
+ end
+
+ end
+
+ end
+
+ def _finish(final = true)
+
+ if final && @zstack.empty?
+ raise("No z calls made in 2.5d script")
+ end
+
+ super(final)
+
+ if final
+
+ view = RBA::LayoutView::current.open_d25_view
+
+ begin
+
+ displays = {}
+
+ @zstack.each do |z|
+ (displays[z.display.object_id] ||= []) << z
+ end
+
+ displays.each do |k,zz|
+ display = zz[0].display
+ view.open_display(display.frame, display.fill, display.like)
+ zz.each do |z|
+ view.entry(z.layer, z.start, z.zstop)
+ end
+ view.close_display
+ end
+
+ view.finish
+
+ rescue => ex
+ view.close
+ raise ex
+ end
+
+ end
+
+ end
+
+ end
+
+end
+
diff --git a/src/plugins/tools/view_25d/lay_plugin/built-in-macros/d25_install.lym b/src/plugins/tools/view_25d/lay_plugin/built-in-macros/d25_install.lym
new file mode 100644
index 000000000..6e0ebc18f
--- /dev/null
+++ b/src/plugins/tools/view_25d/lay_plugin/built-in-macros/d25_install.lym
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+ true
+ false
+
+ false
+
+
+ ruby
+
+
+module D25
+
+ # Installs the home menu entries (needs to be done on autorun, not autorun-early)
+
+ if RBA::Application::instance && RBA::Application::instance.main_window
+
+ cat = "d25"
+ name = "2.5d"
+
+ mw = RBA::Application::instance.main_window
+ mw.menu.insert_menu("tools_menu.verification_group+", "d25", "2.5d View")
+
+ @new_action = RBA::Action::new
+ @new_action.title = "New #{name} Script"
+ @new_action.on_triggered do
+ mw.show_macro_editor(cat, true)
+ end
+
+ mw.menu.insert_item("tools_menu.#{cat}.end", "new_script", @new_action)
+
+ @edit_action = RBA::Action::new
+ @edit_action.title = "Edit #{name} Script"
+ @edit_action.on_triggered do
+ mw.show_macro_editor(cat, false)
+ end
+
+ mw.menu.insert_item("tools_menu.#{cat}.end", "edit_script", @edit_action)
+
+ end
+
+end
+
+
+
diff --git a/src/plugins/tools/view_25d/lay_plugin/built-in-macros/d25_interpreters.lym b/src/plugins/tools/view_25d/lay_plugin/built-in-macros/d25_interpreters.lym
new file mode 100644
index 000000000..fbf0dc9ef
--- /dev/null
+++ b/src/plugins/tools/view_25d/lay_plugin/built-in-macros/d25_interpreters.lym
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+ false
+ true
+
+ false
+
+
+ ruby
+
+
+module D25
+
+ class D25Executable < RBA::Executable
+
+ def initialize(macro, generator, rdb_index = nil)
+
+ @d25 = D25Engine::new
+ @d25._rdb_index = rdb_index
+ @d25._generator = generator
+
+ @macro = macro
+
+ end
+
+ def execute
+
+ @d25._start("D25: " + @macro.path)
+
+ # Set a debugger scope so that our errors end up with the debugger set to the D25's line
+ RBA::MacroExecutionContext::set_debugger_scope(@macro.path)
+
+ begin
+
+ # No verbosity set in d25 engine - we cannot use the engine's logger
+ RBA::Logger::verbosity >= 10 && RBA::Logger::info("Running #{@macro.path}")
+ @d25.instance_eval(@macro.text, @macro.path)
+
+ rescue => ex
+
+ @d25.error("In #{@macro.path}: #{ex.to_s}")
+ RBA::MacroExecutionContext::ignore_next_exception
+ raise ex
+
+ end
+
+ nil
+
+ end
+
+ def cleanup
+
+ # Remove the debugger scope
+ RBA::MacroExecutionContext::remove_debugger_scope
+
+ # cleans up and creates layout and report views
+ @d25._finish
+
+ end
+
+ end
+
+ # A DSL implementation for a D25 language (XML format)
+ class D25Interpreter < RBA::MacroInterpreter
+
+ # Constructor
+ def initialize(recipe)
+
+ @recipe = recipe
+
+ # Make the DSL use ruby syntax highlighting
+ self.syntax_scheme = "ruby"
+ self.suffix = "lyd25"
+ self.debugger_scheme = RBA::MacroInterpreter::RubyDebugger
+ self.storage_scheme = RBA::MacroInterpreter::MacroFormat
+ self.description = "D25"
+
+ # Registers the new interpreter
+ register("d25-dsl-xml")
+
+ # create a template for the macro editor:
+ create_template(":/d25-templates/d25.lym")
+
+ # if available, create a menu branch
+ if RBA::Application::instance && RBA::Application::instance.main_window
+ mw = RBA::Application::instance.main_window
+ mw.menu.insert_menu("tools_menu.verification_group+", "d25", "2.5d View")
+ end
+
+ end
+
+ # Implements the execute method
+ def executable(macro)
+ D25Executable::new(macro, @recipe.generator("script" => macro.path))
+ end
+
+ end
+
+ # A DSL implementation for a D25 language (Plain text format)
+ class D25PlainTextInterpreter < RBA::MacroInterpreter
+
+ # Constructor
+ def initialize(recipe)
+
+ @recipe = recipe
+
+ # Make the DSL use ruby syntax highlighting
+ self.syntax_scheme = "ruby"
+ self.suffix = "d25"
+ self.debugger_scheme = RBA::MacroInterpreter::RubyDebugger
+ self.storage_scheme = RBA::MacroInterpreter::PlainTextWithHashAnnotationsFormat
+ self.description = "D25 (Text)"
+
+ # Registers the new interpreter
+ register("d25-dsl")
+
+ end
+
+ # Implements the execute method
+ def executable(macro)
+ D25Executable::new(macro, @recipe.generator("script" => macro.path))
+ end
+
+ end
+
+ # A recipe implementation allowing the D25 run to be redone
+ class D25Recipe < RBA::Recipe
+
+ def initialize
+ super("d25", "D25 recipe")
+ end
+
+ def executable(params)
+
+ script = params["script"]
+ if ! script
+ return
+ end
+
+ macro = RBA::Macro::macro_by_path(script)
+ macro || raise("Can't find D25 script #{script} - unable to re-run")
+
+ D25Executable::new(macro, self.generator("script" => script), params["rdb_index"])
+
+ end
+
+ end
+
+ # Register the recipe
+ d25_recipe = D25Recipe::new
+
+ # Register the new interpreters
+ D25Interpreter::new(d25_recipe)
+ D25PlainTextInterpreter::new(d25_recipe)
+
+ # Creates a new macro category
+ if RBA::Application::instance
+ RBA::Application::instance.add_macro_category("d25", "2.5d view", [ "d25" ])
+ end
+
+end
+
+
+
diff --git a/src/plugins/tools/view_25d/lay_plugin/layD25Resources.qrc b/src/plugins/tools/view_25d/lay_plugin/layD25Resources.qrc
new file mode 100644
index 000000000..be37f46fe
--- /dev/null
+++ b/src/plugins/tools/view_25d/lay_plugin/layD25Resources.qrc
@@ -0,0 +1,10 @@
+
+
+ built-in-macros/_d25_engine.rb
+ built-in-macros/d25_interpreters.lym
+ built-in-macros/d25_install.lym
+
+
+ templates/d25.lym
+
+
diff --git a/src/plugins/tools/view_25d/lay_plugin/lay_plugin.pro b/src/plugins/tools/view_25d/lay_plugin/lay_plugin.pro
index b05f93e94..cc6063840 100644
--- a/src/plugins/tools/view_25d/lay_plugin/lay_plugin.pro
+++ b/src/plugins/tools/view_25d/lay_plugin/lay_plugin.pro
@@ -26,6 +26,9 @@ SOURCES = \
FORMS = \
D25View.ui \
+RESOURCES = \
+ layD25Resources.qrc \
+
greaterThan(QT_MAJOR_VERSION, 5) {
QT += openglwidgets
}
diff --git a/src/plugins/tools/view_25d/lay_plugin/templates/d25.lym b/src/plugins/tools/view_25d/lay_plugin/templates/d25.lym
new file mode 100644
index 000000000..ac0468824
--- /dev/null
+++ b/src/plugins/tools/view_25d/lay_plugin/templates/d25.lym
@@ -0,0 +1,59 @@
+
+
+ General;;2.5d view generator script (*.lyd25)\nA script generating a 2.5d view
+
+ d25
+
+
+
+ false
+ false
+
+ true
+ d25_scripts
+ tools_menu.d25.end
+ ruby
+
+
+# Read about 2.5d generator scripts in the User Manual in "Various Topics/The 2.5d View"
+
+# The script utilizes the DRC language with these two extensions
+#
+# z(layer, options ...):
+#
+# This function generates a extruded view from the given layer.
+# The "options" describe the z extension. Valid forms are:
+#
+# z(layer, 0.1 .. 0.2) extrude layer to z = 0.1 to 0.2 um
+# z(layer, zstart: 0.1, zstop: 0.2) same as above
+# z(layer, zstart: 0.1, height: 0.1) same as above, but with height instead of zstop
+# z(layer, height: 200.nm) extrude layer from last z position with a height of 200nm
+#
+# If layer is an original layer, the display options (colors, hollow) are taken
+# from the original layer's display style. Otherwise KLayout decides about the
+# initial display options. Use "display(...)" to control the display options.
+#
+# display(options) { block }
+# display(z(...), z(...), ..., options):
+#
+# Specify the display options to use for the layers generated inside the block
+# (which must contains at least one "z" call) or the given z calls:
+#
+# Options are:
+#
+# display(..., color: 0xff0000) use bright red for the material color (RGB)
+# display(..., frame: 0xff0000) use bright red for the frame color (combine with "color" for the fill)
+# display(..., hollow: true) use hollow style (only frame is drawn)
+# display(..., like: "7/0") borrow style from layout view's style for layer "7/0"
+#
+
+z(input(1, 0), 0.1 .. 0.2)
+
+display(like: 7/0) do
+ z(input(7, 0), height: 100.nm)
+ z(input(8, 0), height: 150.nm)
+end
+
+
+
+