Skip to content
This repository has been archived by the owner on Jul 29, 2024. It is now read-only.

Latest commit

 

History

History
174 lines (128 loc) · 5.74 KB

hacking.md

File metadata and controls

174 lines (128 loc) · 5.74 KB

ImplicitCAD Hacking How To

So you want to improve ImplicitCAD. Yay! More help is a good thing.

As of the time of writing, ImplicitCAD has 3417 lines of code, 896 lines of comments, and 877 blank lines, for a total of 5190 lines spread over 42 files. For a project of ImplicitCAD's scope, that's pretty small, but it's still enough that it can be difficult to find the section we need to change...

The structure of ImplicitCAD is as follows:

Graphics
└── Implicit
    ├── Export
    │   ├── Render
    │   └── Symbolic
    ├── ExtOpenScad
    │   └── Util
    └── ObjectUtil

Graphics.Implicit.Export is, as you may guess, where all the export stuff is. Graphics.Implicit.ExtOpenScad is the programming language interpreter for the ExtOpenScad language, our extention of openscad. Finally, the graphics engine is defined in Graphics.Implicit and Graphics.Implicit.ObjectUtil.

The rest of this file will go through different changes you are likely to want to make and how to implement them.

Language Changes

Most likely, you want to change one of four things:

  • Expressions: Expressions are things like 1+2, "abc", and [sin(3.14), pi]. They are defined in Graphics.Implicit.ExtOpenScad.Expressions. (Note that sin and pi are variables, which are defined elsewhere.)

  • Statements: Statements are things like variable assignment, for loops, and if statements. For example for (a = [1,2,3]) echo (a);. Statements are defined in Graphics.Implicit.ExtOpenScad.Statements.

computationStatement = ...
			ifStatement,
			forStatement,
			...

...

forStatement = (do
	line <- fmap sourceLine getPosition
	-- a for loop is of the form:
	--      for ( vsymb = vexpr   ) loopStatements
	-- eg.  for ( a     = [1,2,3] ) {echo(a);   echo "lol";}
	-- eg.  for ( [a,b] = [[1,2]] ) {echo(a+b); echo "lol";}
	string "for"
	many space
	char '('
	many space
	pattern <- patternMatcher
	many space
	char '='
	vexpr <- expression 0
	char ')'
	many space
	loopStatements <- suite
	...
  • Default Variables: Like sin, pi, sqrt. These are all defined in Graphics.Implicit.ExtOpenScad.Default. We can just use toOObj to convert Haskell values into OpenscadObjs and use them as default variable settings. (Small caveat: inputs to toOObj can't be polymorphic, so we use a type signature to force it to a certain type.)
defaultFunctions = map (\(a,b) -> (a, toOObj ( b ::  -> )))
	[
		("sin",   sin),
		("cos",   cos),
		("tan",   tan),
		...
	]
  • Default Modules: Like sphere and linear_extrude. These are all defined in Graphics.Implicit.ExtOpenScad.Primitives.
primitives = [ sphere, cube, square, cylinder, ... ]

...

-- **Exmaple of implementing a module**
-- sphere is a module without a suite named sphere,
-- this means that the parser will look for this like
--       sphere(args...);
sphere = moduleWithoutSuite "sphere" $ do
	example "sphere(3);"
	example "sphere(r=5);"
	-- What are the arguments?
	-- The radius, r, which is a (real) number.
	-- Because we don't provide a default, this ends right
	-- here if it doesn't get a suitable argument!
	r ::  <- argument "r" 
	            `doc` "radius of the sphere"
	-- So what does this module do?
	-- It adds a 3D object, a sphere of radius r,
	-- using the sphere implementation in Prim
	-- (Graphics.Implicit.Primitives)
	addObj3 $ Prim.sphere r

Output Formats

Formats are defined in files like Graphics.Implicit.Export.TriangleMeshFormats (as is the case with STLs), Graphics.Implicit.Export.PolylineMeshFormats (as is the case with SVGs).

Then, in Graphics.Implicit.Export:

writeSVG res = writeObject res PolylineFormats.svg
writeSTL res = writeObject res  TriangleMeshFormats.stl

Rendering Algorithms

These are defined in Graphics.Implicit.Export.Render and children. Graphics.Implicit.Export.Render begins with an outline of how rendering is done:

-- Here's the plan for rendering a cube (the 2D case is trivial):

-- (1) We calculate midpoints using interpolate.
--     This guarentees that our mesh will line up everywhere.
--     (Contrast with calculating them in getSegs)

import Graphics.Implicit.Export.Render.Interpolate (interpolate)

...

If you are interested on working on this part of the code, read it. The children are also well documented.

Graphics Primitives

The most complicated part of ImplicitCAD is the actual graphics engine. Before working on it, please familiarize yourself with the theory as described in Chris' blog post (though changes have occured since then).

The simples way to implement a new primitive is using implicit, a contructor that takes an implicit function and boudning box, producing an object. For example, we could have originally defined sphere as:

sphere ::  -> SymbolicObj3
sphere r = implicit (
	\(x,y,z) -> sqrt (x^2+y^2+z^2) - r,
	((-r, -r, -r), (r, r, r))
	)

and put it in Graphics.Implicit.Primitives. However, to allow more powerful optimizations, meta-inspection, and other goodies, frequently used objects should be put in the SymbolicObj definitions in Graphics.Implicit.Definitions. For example, sphere:

data SymbolicObj3 = 
	  Rect3R  ℝ3 ℝ3
	| Sphere 
	...

Then one needs to make the relevant entries in Graphics.Implicit.ObjectUtil.*.

Graphics.Implicit.ObjectUtil.Box3:

getBox3 (Sphere r ) = ((-r, -r, -r), (r,r,r))

Graphics.Implicit.ObjectUtil.GetImplicit3:

getImplicit3 (Sphere r ) = 
	\(x,y,z) -> sqrt (x**2 + y**2 + z**2) - r