Merge branch 'master' into master

This commit is contained in:
Julia Longtin 2019-06-06 12:07:44 +01:00 committed by GitHub
commit d7333da123
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
75 changed files with 2885 additions and 1885 deletions

3
.gitignore vendored
View File

@ -7,3 +7,6 @@
*.stl
dist/
Setup
docs/iscad.md
.stack-work/
Examples/*cachegrind*

View File

@ -20,6 +20,8 @@ chicagoduane -- Duane Johnson -- Duane.Johnson@gmail.com
l29ah -- Sergey Alirzaev -- zl29ah@gmail.com
firegurafiku -- Pavel Kretov -- firegurafiku@gmail.com
gambogi -- Matthew Gambogi -- m@gambogi.com
cookshak -- Kelvin Cookshaw -- kelvin@cookshaw.com
kpe -- ?? -- ??
Thanks as well, to raghuugare. Due to not being contactable,
his code has been removed during the license update.

View File

@ -6,4 +6,4 @@ out = union [
translate (40,40) (circle 30) ]
main = writeSVG 2 "example11.svg" out

View File

@ -4,5 +4,5 @@ import Graphics.Implicit
out = unionR 14 [
rectR 0 (-40,-40) (40,40),
translate (40,40) (circle 30) ]
main = writeSVG 2 "example12.svg" out

10
Examples/example14.escad Normal file
View File

@ -0,0 +1,10 @@
// example7.escad -- A twisted rounded extrusion of the rounded union of 5 hexagonical solids.
linear_extrude (height = 40, center=true, twist=90, r=5){
union ( r = 8) {
circle (10,$fn=6);
translate ([22,0]) circle (10,$fn=6);
translate ([0,22]) circle (10,$fn=6);
translate ([-22,0]) circle (10,$fn=6);
translate ([0,-22]) circle (10,$fn=6);
}
}

4
Examples/example15.scad Normal file
View File

@ -0,0 +1,4 @@
difference() {
sphere(20);
cylinder(r=17, h=100, center = true);
}

8
Examples/example16.hs Normal file
View File

@ -0,0 +1,8 @@
import Graphics.Implicit
import Graphics.Implicit.Definitions
import Graphics.Implicit.Primitives
roundbox:: SymbolicObj3
roundbox = implicit (\(x,y,z) -> (x^4 + y^4 + z^4 - 15000)) ((-20,-20,-20),(20,20,20))
main = writeSTL 2 "example16.stl" roundbox

21
Examples/example17.hs Normal file
View File

@ -0,0 +1,21 @@
-- Example 17, pulled from our benchmarking suite.
import Graphics.Implicit
import Graphics.Implicit.Definitions
default (Fast, )
object2 :: SymbolicObj3
object2 = squarePipe (10,10,10) 1 100
where
squarePipe :: (,,) -> -> -> SymbolicObj3
squarePipe (x,y,z) diameter precision =
union
$ map (\start-> translate start
$ rect3R 0 (0,0,0) (diameter,diameter,diameter)
)
$ zip3 (map (\n->((fromIntegral n)/precision)*x) [0..100])
(map (\n->((fromIntegral n)/precision)*y) [0..100])
(map (\n->((fromIntegral n)/precision)*z) [0..100])
main = writeSTL 1 "example17.stl" object2

View File

@ -3,69 +3,43 @@
-- Released under the GNU AGPLV3+, see LICENSE
-- FIXME: Required. why?
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances, FlexibleContexts, TypeSynonymInstances, UndecidableInstances #-}
{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FlexibleContexts, TypeSynonymInstances, UndecidableInstances #-}
{- The purpose of this file is to pass on the functionality we want
to be accessible to an end user who is compiling objects using
this haskell library. -}
-- MAYBEFIXME: impliment slice operation , regularPolygon and zsurface primitives.
module Graphics.Implicit(
-- Operations
translate,
scale,
complement, union, intersect, difference,
unionR, intersectR, differenceR,
shell,
extrudeR,
extrudeRotateR,
extrudeRM,
extrudeOnEdgeOf,
-- Primitives
sphere,
rect3R,
circle,
cylinder,
cylinder2,
rectR,
polygonR,
rotateExtrude,
rotate3,
rotate3V,
pack3,
rotate,
pack2,
-- Export
writeSVG,
writeSTL,
writeBinSTL,
writeOBJ,
writeTHREEJS,
writeSCAD2,
writeSCAD3,
writeGCodeHacklabLaser,
writePNG2,
writePNG3,
runOpenscad,
implicit,
SymbolicObj2,
SymbolicObj3
module Graphics.Implicit (
module P,
module E,
module W,
writeSVG,
writeDXF2,
writeSTL,
writeBinSTL,
writeOBJ,
writeTHREEJS,
writeSCAD2,
writeSCAD3,
writeGCodeHacklabLaser,
writePNG2,
writePNG3
) where
import Prelude(FilePath, IO)
-- The primitive objects, and functions for manipulating them.
import Graphics.Implicit.Primitives (translate, scale, complement, union, intersect, difference, unionR, intersectR, differenceR, shell, extrudeR, extrudeRM, extrudeRotateR, extrudeOnEdgeOf, sphere, rect3R, circle, cylinder, cylinder2, rectR, polygonR, rotateExtrude, rotate3, rotate3V, pack3, rotate, pack2, implicit)
-- MAYBEFIXME: impliment slice operation, regularPolygon and zsurface primitives.
import Graphics.Implicit.Primitives as P (translate, scale, complement, union, intersect, difference, unionR, intersectR, differenceR, shell, extrudeR, extrudeRM, extrudeRotateR, extrudeOnEdgeOf, sphere, rect3R, circle, cylinder, cylinder2, rectR, polygonR, rotateExtrude, rotate3, rotate3V, pack3, rotate, pack2, implicit)
-- The Extended OpenScad interpreter.
import Graphics.Implicit.ExtOpenScad (runOpenscad)
import Graphics.Implicit.ExtOpenScad as E (runOpenscad)
-- typesclasses and types defining the world, or part of the world.
import Graphics.Implicit.Definitions as W (, SymbolicObj2, SymbolicObj3)
-- Functions for writing files based on the result of operations on primitives.
import qualified Graphics.Implicit.Export as Export (writeSVG, writeSTL, writeBinSTL, writeOBJ, writeSCAD2, writeSCAD3, writeTHREEJS, writeGCodeHacklabLaser, writePNG)
-- Datatypes/classes defining the world, or part of the world.
import Graphics.Implicit.Definitions (, SymbolicObj2, SymbolicObj3)
import qualified Graphics.Implicit.Export as Export (writeSVG, writeDXF2, writeSTL, writeBinSTL, writeOBJ, writeSCAD2, writeSCAD3, writeTHREEJS, writeGCodeHacklabLaser, writePNG)
-- We want Export to be a bit less polymorphic
-- (so that types will collapse nicely)
@ -73,6 +47,9 @@ import Graphics.Implicit.Definitions (, SymbolicObj2, SymbolicObj3)
writeSVG :: -> FilePath -> SymbolicObj2 -> IO ()
writeSVG = Export.writeSVG
writeDXF2 :: -> FilePath -> SymbolicObj2 -> IO ()
writeDXF2 = Export.writeDXF2
writeSTL :: -> FilePath -> SymbolicObj3 -> IO ()
writeSTL = Export.writeSTL

View File

@ -1,30 +1,36 @@
-- Implicit CAD. Copyright (C) 2011, Christopher Olah (chris@colah.ca)
-- Copyright 2014 2015 2016, Julia Longtin (julial@turinglace.com)
-- Copyright 2014 2015 2016, 2017, 2018, Julia Longtin (julial@turinglace.com)
-- Copyright 2015 2016, Mike MacHenry (mike.machenry@gmail.com)
-- Released under the GNU AGPLV3+, see LICENSE
-- This module deliberately declares orphan instances of Show.
{-# OPTIONS_GHC -fno-warn-orphans #-}
-- Allow us to use explicit foralls when writing function type declarations.
{-# LANGUAGE ExplicitForAll #-}
-- Required. FIXME: why?
{-# LANGUAGE FlexibleInstances #-}
-- Definitions of the types used when modeling, and a few operators.
module Graphics.Implicit.Definitions (
module F,
module N,
,
2,
both,
3,
allthree,
min,
,
(),
(*),
(/),
Polyline,
Triangle,
NormedTriangle,
TriangleMesh,
NormedTriangleMesh,
Polyline(Polyline),
Polytri(Polytri),
Triangle(Triangle),
NormedTriangle(NormedTriangle),
TriangleMesh(TriangleMesh),
NormedTriangleMesh(NormedTriangleMesh),
Obj2,
Obj3,
Box2,
@ -67,38 +73,70 @@ module Graphics.Implicit.Definitions (
ExtrudeRM,
ExtrudeOnEdgeOf,
RotateExtrude),
Rectilinear2,
Rectilinear3,
fromto,
fromFastto,
fromtoFloat
)
where
import Prelude (Show, Double, Integer, Maybe, Either, show, (*), (/))
import Prelude (Show, Double, Either, show, (*), (/), fromIntegral, Float, realToFrac)
import Data.Maybe (Maybe)
import Data.VectorSpace (Scalar, InnerSpace, (<.>))
import Graphics.Implicit.FastIntUtil as F (Fast(Fast), fromFast, toFast)
import Graphics.Implicit.IntegralUtil as N (, from, to)
import Control.DeepSeq (NFData, rnf)
-- Let's make things a bit nicer.
-- Following the math notation , ℝ², ℝ³...
-- Supports changing Float to Double for more precision!
-- FIXME: what about using rationals instead of Float/Double?
type = Double
type 2 = (,)
type 3 = (,,)
-- | A give up point for dividing s
min ::
-- for Doubles.
min = 0.0000000000000002
-- for Floats.
--min = 0.00000011920928955078125 * 2
-- for Doubles.
min = 0.0000000000000002
-- | apply a function to both items in the provided tuple.
both :: forall t b. (t -> b) -> (t, t) -> (b, b)
both f (x,y) = (f x, f y)
{-# INLINABLE both #-}
type = Integer
-- | apply a function to all three items in the provided tuple.
allthree :: forall t b. (t -> b) -> (t, t, t) -> (b, b, b)
allthree f (x,y,z) = (f x, f y, f z)
{-# INLINABLE allthree #-}
-- TODO: Find a better place for this
-- | TODO: Find a better place for this
() :: InnerSpace a => a -> a -> Scalar a
() = (<.>)
{-# INLINABLE (⋅) #-}
-- Wrap the functions that convert datatypes.
-- handle additional instances of Show.
-- | Convert from our Integral to our Rational.
fromto :: ->
fromto = fromIntegral
{-# INLINABLE fromto #-}
-- | Convert from our Fast Integer (int32) to .
fromFastto :: Fast ->
fromFastto (Fast a) = fromIntegral a
{-# INLINABLE fromFastto #-}
-- | Convert from our rational to a float, for output.
fromtoFloat :: -> Float
fromtoFloat = realToFrac
{-# INLINABLE fromtoFloat #-}
-- |add aditional instances to Show, for when we dump the intermediate form of objects.
instance Show ( -> ) where
show _ = "<function >"
@ -117,50 +155,72 @@ instance Show (3 -> ) where
--instance Show BoxedObj3 where
-- show _ = "<BoxedObj3>"
-- TODO: Find a better way to do this?
-- | Add multiply and divide operators for two 2s or 3s.
class ComponentWiseMultable a where
(*) :: a -> a -> a
(/) :: a -> a -> a
instance ComponentWiseMultable 2 where
(x,y) * (x',y') = (x*x', y*y')
{-# INLINABLE (⋯*) #-}
(x,y) / (x',y') = (x/x', y/y')
{-# INLINABLE (⋯/) #-}
instance ComponentWiseMultable 3 where
(x,y,z) * (x',y',z') = (x*x', y*y', z*z')
{-# INLINABLE (⋯*) #-}
(x,y,z) / (x',y',z') = (x/x', y/y', z/z')
{-# INLINABLE (⋯/) #-}
-- | A chain of line segments, as in SVG
-- | A chain of line segments, as in SVG or DXF.
-- eg. [(0,0), (0.5,1), (1,0)] ---> /\
type Polyline = [2]
newtype Polyline = Polyline [2]
-- | A triangle (a,b,c) = a triangle with vertices a, b and c
type Triangle = (3, 3, 3)
-- | A triangle in 2D space (a,b,c).
newtype Polytri = Polytri (2, 2, 2)
-- | A triangle in 3D space (a,b,c) = a triangle with vertices a, b and c
newtype Triangle = Triangle (3, 3, 3)
-- | A triangle ((v1,n1),(v2,n2),(v3,n3)) has vertices v1, v2, v3
-- with corresponding normals n1, n2, and n3
type NormedTriangle = ((3, 3), (3, 3), (3, 3))
newtype NormedTriangle = NormedTriangle ((3, 3), (3, 3), (3, 3))
-- | A triangle mesh is a bunch of triangles :)
type TriangleMesh = [Triangle]
-- | A triangle mesh is a bunch of triangles, attempting to be a surface.
newtype TriangleMesh = TriangleMesh [Triangle]
-- | A normed triangle mesh is a bunch of normed trianlges!!
type NormedTriangleMesh = [NormedTriangle]
-- | A normed triangle mesh is a mesh of normed triangles.
newtype NormedTriangleMesh = NormedTriangleMesh [NormedTriangle]
-- | A 2D object
instance NFData Triangle where
rnf (Triangle (a,b,c)) = rnf (a,b,c)
instance NFData TriangleMesh where
rnf (TriangleMesh xs) = rnf xs
instance NFData Polytri where
rnf (Polytri (a,b,c)) = rnf (a,b,c)
instance NFData Polyline where
rnf (Polyline xs) = rnf xs
-- | A 2D object.
type Obj2 = (2 -> )
-- | A 3D object
-- | A 3D object.
type Obj3 = (3 -> )
-- | A 2D box
-- | A 2D box.
type Box2 = (2, 2)
-- | A 3D box
-- | A 3D box.
type Box3 = (3, 3)
-- | A Box for containing a 2D object
-- | A Box containing a 2D object.
type Boxed2 a = (a, Box2)
-- | A Box for containing a 3D object
-- | A Box containing a 3D object.
type Boxed3 a = (a, Box3)
-- | A Boxed 2D object
@ -170,7 +230,7 @@ type BoxedObj2 = Boxed2 Obj2
type BoxedObj3 = Boxed3 Obj3
-- | A symbolic 2D object format.
-- We want to have a symbolic object so that we can
-- We want to have symbolic objects so that we can
-- accelerate rendering & give ideal meshes for simple
-- cases.
data SymbolicObj2 =
@ -195,11 +255,11 @@ data SymbolicObj2 =
deriving Show
-- | A symbolic 3D format!
data SymbolicObj3 =
data SymbolicObj3 =
-- Primitives
Rect3R 3 3
| Sphere
| Cylinder
Rect3R 3 3 -- rounding, start, stop.
| Sphere -- radius
| Cylinder --
-- (Rounded) CSG
| Complement3 SymbolicObj3
| UnionR3 [SymbolicObj3]
@ -219,7 +279,7 @@ data SymbolicObj3 =
| ExtrudeR SymbolicObj2
| ExtrudeRotateR SymbolicObj2
| ExtrudeRM
-- rounding radius
-- rounding radius (ignored)
(Maybe ( -> )) -- twist
(Maybe ( -> )) -- scale
(Maybe ( -> 2)) -- translate
@ -227,16 +287,11 @@ data SymbolicObj3 =
(Either (2 -> )) -- height to extrude to
| RotateExtrude
-- Angle to sweep to
(Maybe ) -- Loop or path (rounded corner)
(Maybe ) -- Loop or path (rounded corner) (ignored)
(Either 2 ( -> 2)) -- translate function
(Either ( -> )) -- rotate function
SymbolicObj2 -- object to extrude
| ExtrudeOnEdgeOf SymbolicObj2 SymbolicObj2
deriving Show
-- | Rectilinear 2D set
type Rectilinear2 = [Box2]
-- | Rectilinear 2D set
type Rectilinear3 = [Box3]

View File

@ -6,17 +6,17 @@
-- Allow us to use explicit foralls when writing function type declarations.
{-# LANGUAGE ExplicitForAll #-}
-- FIXME: Required. why?
-- Allow us to use real types in the type constraints.
{-# LANGUAGE FlexibleContexts #-}
module Graphics.Implicit.Export (writeObject, formatObject, writeSVG, writeSTL, writeBinSTL, writeOBJ, writeTHREEJS, writeGCodeHacklabLaser, writeSCAD3, writeSCAD2, writePNG) where
module Graphics.Implicit.Export (writeObject, formatObject, writeSVG, writeSTL, writeBinSTL, writeOBJ, writeTHREEJS, writeGCodeHacklabLaser, writeDXF2, writeSCAD2, writeSCAD3, writePNG) where
import Prelude (FilePath, IO, (.), ($))
-- The types of our objects (before rendering), and the type of the resolution to render with.
import Graphics.Implicit.Definitions (SymbolicObj2, SymbolicObj3, , Polyline, TriangleMesh, Triangle, NormedTriangle)
import Graphics.Implicit.Definitions (SymbolicObj2, SymbolicObj3, , Polyline, TriangleMesh, NormedTriangleMesh)
-- The functions for writing our output, as well as a type used.
-- functions for outputing a file, and one of the types.
import Data.Text.Lazy (Text)
import qualified Data.Text.Lazy.IO as LT (writeFile)
import qualified Data.ByteString.Lazy as LBS (writeFile)
@ -24,25 +24,26 @@ import qualified Data.ByteString.Lazy as LBS (writeFile)
-- Import instances of DiscreteApproxable...
import Graphics.Implicit.Export.DiscreteAproxable (DiscreteAproxable, discreteAprox)
-- Object formats
import qualified Graphics.Implicit.Export.PolylineFormats as PolylineFormats (svg, hacklabLaserGCode)
-- Output file formats.
import qualified Graphics.Implicit.Export.PolylineFormats as PolylineFormats (svg, hacklabLaserGCode, dxf2)
import qualified Graphics.Implicit.Export.TriangleMeshFormats as TriangleMeshFormats (stl, binaryStl, jsTHREE)
import qualified Graphics.Implicit.Export.NormedTriangleMeshFormats as NormedTriangleMeshFormats (obj)
import qualified Graphics.Implicit.Export.SymbolicFormats as SymbolicFormats (scad3, scad2)
import qualified Graphics.Implicit.Export.SymbolicFormats as SymbolicFormats (scad2, scad3)
import qualified Codec.Picture as ImageFormatCodecs (DynamicImage, savePngImage)
-- Write an object using the given format function.
-- | Write an object to a file with LazyText IO, using the given format writer function.
writeObject :: (DiscreteAproxable obj aprox)
=> -- ^ Resolution
-> (aprox -> Text) -- ^ File Format (Function that formats)
-> (aprox -> Text) -- ^ File Format Writer (Function that formats)
-> FilePath -- ^ File Name
-> obj -- ^ Object to render
-> IO () -- ^ Writing Action!
writeObject res format filename obj =
let aprox = formatObject res format obj
writeObject res formatWriter filename obj =
let
aprox = formatObject res formatWriter obj
in LT.writeFile filename aprox
-- Write an object using the given format writer.
-- | Serialize an object using the given format writer, which takes the filename and writes to it..
writeObject' :: (DiscreteAproxable obj aprox)
=> -- ^ Resolution
-> (FilePath -> aprox -> IO ()) -- ^ File Format writer
@ -50,26 +51,29 @@ writeObject' :: (DiscreteAproxable obj aprox)
-> obj -- ^ Object to render
-> IO () -- ^ Writing Action!
writeObject' res formatWriter filename obj =
let aprox = discreteAprox res obj
in formatWriter filename aprox
formatWriter filename (discreteAprox res obj)
-- | Serialize an object using the given format writer. No file target is implied.
formatObject :: (DiscreteAproxable obj aprox)
=> -- ^ Resolution
-> (aprox -> Text) -- ^ File Format (Function that formats)
-> (aprox -> Text) -- ^ File Format Writer (Function that formats)
-> obj -- ^ Object to render
-> Text -- ^ Resulting lazy ByteString
formatObject res format = format . discreteAprox res
formatObject res formatWriter = formatWriter . discreteAprox res
writeSVG :: forall obj. DiscreteAproxable obj [Polyline] => -> FilePath -> obj -> IO ()
writeSVG res = writeObject res PolylineFormats.svg
writeSTL :: forall obj. DiscreteAproxable obj [Triangle] => -> FilePath -> obj -> IO ()
writeDXF2 :: forall obj. DiscreteAproxable obj [Polyline] => -> FilePath -> obj -> IO ()
writeDXF2 res = writeObject res PolylineFormats.dxf2
writeSTL :: forall obj. DiscreteAproxable obj TriangleMesh => -> FilePath -> obj -> IO ()
writeSTL res = writeObject res TriangleMeshFormats.stl
writeBinSTL :: forall obj. DiscreteAproxable obj [Triangle] => -> FilePath -> obj -> IO ()
writeBinSTL :: forall obj. DiscreteAproxable obj TriangleMesh => -> FilePath -> obj -> IO ()
writeBinSTL res file obj = LBS.writeFile file $ TriangleMeshFormats.binaryStl $ discreteAprox res obj
writeOBJ :: forall obj. DiscreteAproxable obj [NormedTriangle] => -> FilePath -> obj -> IO ()
writeOBJ :: forall obj. DiscreteAproxable obj NormedTriangleMesh => -> FilePath -> obj -> IO ()
writeOBJ res = writeObject res NormedTriangleMeshFormats.obj
writeTHREEJS :: forall obj. DiscreteAproxable obj TriangleMesh => -> FilePath -> obj -> IO ()

View File

@ -5,101 +5,117 @@
-- Allow our DiscreteAproxable class to handle multiple parameters.
{-# LANGUAGE MultiParamTypeClasses #-}
-- FIXME: why is this here?
{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}
-- For the instance declaration of DiscreteAproxable SymbolicObj2 [Polyline]
{-# LANGUAGE FlexibleInstances #-}
module Graphics.Implicit.Export.DiscreteAproxable where
-- | A module for retrieving approximate represententations of objects.
module Graphics.Implicit.Export.DiscreteAproxable (DiscreteAproxable, discreteAprox) where
import Prelude(Int, (-), (/), ($), (<), map, round, (+), maximum, abs, (*), fromIntegral, max, realToFrac)
import Prelude((-), (/), ($), (<), map, round, (+), maximum, abs, (*), fromIntegral, max, realToFrac, Int)
import Graphics.Implicit.Definitions (, 2, SymbolicObj3, SymbolicObj2, Polyline, TriangleMesh, NormedTriangleMesh)
-- Definitions for our number system, objects, and the things we can use to approximately represent objects.
import Graphics.Implicit.Definitions (, 2, SymbolicObj2, SymbolicObj3, Polyline, Triangle, TriangleMesh(TriangleMesh), NormedTriangleMesh(NormedTriangleMesh))
import Graphics.Implicit.ObjectUtil (getImplicit3, getImplicit2, getBox3, getBox2)
import Graphics.Implicit.ObjectUtil (getImplicit2, getImplicit3, getBox2, getBox3)
import Graphics.Implicit.Export.SymbolicObj3 (symbolicGetMesh)
import Graphics.Implicit.Export.SymbolicObj2 (symbolicGetContour)
import Graphics.Implicit.Export.Util (normTriangle)
import Graphics.Implicit.Export.RayTrace (dynamicImage, Color, average, Camera(Camera), Light(Light), Scene(Scene), traceRay, cameraRay)
import Codec.Picture (DynamicImage, generateImage, PixelRGBA8(PixelRGBA8))
import Graphics.Implicit.Export.SymbolicObj2 (symbolicGetContour)
import Graphics.Implicit.Export.Util (normTriangle)
-- We are the only ones that use this.
import Graphics.Implicit.Export.RayTrace (Color(Color), Camera(Camera), Light(Light), Scene(Scene), average, traceRay, cameraRay)
import Codec.Picture (DynamicImage(ImageRGBA8), PixelRGBA8(PixelRGBA8), generateImage)
import Data.VectorSpace ((^+^), (^/), (*^), (^-^))
import Data.AffineSpace ((.-^), (.+^))
default ()
unmesh :: TriangleMesh -> [Triangle]
unmesh (TriangleMesh m) = m
-- | There is a discrete way to aproximate this object.
-- eg. Aproximating a 3D object with a tirangle mesh
-- eg. Aproximating a 3D object with a triangle mesh
-- would be DiscreteApproxable Obj3 TriangleMesh
class DiscreteAproxable obj aprox where
discreteAprox :: -> obj -> aprox
instance DiscreteAproxable SymbolicObj3 TriangleMesh where
discreteAprox res obj = symbolicGetMesh res obj
discreteAprox = symbolicGetMesh
instance DiscreteAproxable SymbolicObj3 NormedTriangleMesh where
discreteAprox res obj = map (normTriangle res (getImplicit3 obj)) $ symbolicGetMesh res obj
discreteAprox res obj = NormedTriangleMesh $ map (normTriangle res (getImplicit3 obj)) $ unmesh $ symbolicGetMesh res obj
-- FIXME: magic numbers.
-- FIXME: way too many magic numbers.
instance DiscreteAproxable SymbolicObj3 DynamicImage where
discreteAprox _ symbObj = dynamicImage $ generateImage pixelRenderer (round w) (round h)
discreteAprox _ symbObj = ImageRGBA8 $ generateImage pixelRenderer (round w) (round h)
where
-- | Size of the image to produce.
(w,h) = (150, 150) :: 2
obj = getImplicit3 symbObj
box@((x1,y1,z1), (_,y2,z2)) = getBox3 symbObj
av :: -> ->
av a b = (a+b)/(2::)
av a b = (a+b)/2
avY = av y1 y2
avZ = av z1 z2
deviation = maximum [abs $ y1 - avY, abs $ y2 - avY, abs $ z1 - avZ, abs $ z2 - avZ]
camera = Camera (x1-deviation*(2.2::), avY, avZ) (0, -1, 0) (0,0, -1) 1.0
lights = [Light (x1-deviation*(1.5::), y1 - (0.4::)*(y2-y1), avZ) ((0.03::)*deviation) ]
scene = Scene obj (PixelRGBA8 200 200 230 255) lights (PixelRGBA8 255 255 255 0)
pixelRenderer :: Int -> Int -> Color
pixelRenderer a b = renderScreen
((fromIntegral a :: )/w - (0.5::)) ((fromIntegral b :: )/h - (0.5 ::))
renderScreen :: -> -> Color
camera = Camera (x1-deviation*(2.2), avY, avZ) (0, -1, 0) (0,0, -1) 1.0
lights = [Light (x1-deviation*(1.5), y1 - (0.4)*(y2-y1), avZ) ((0.03)*deviation) ]
scene = Scene obj (Color 200 200 230 255) lights (Color 255 255 255 0)
-- | passed to generateImage, it's external, and determines this type.
pixelRenderer :: Int -> Int -> PixelRGBA8
pixelRenderer a b = renderScreen
((fromIntegral a)/w - (0.5)) ((fromIntegral b)/h - (0.5))
renderScreen :: -> -> PixelRGBA8
renderScreen a b =
average $ [
traceRay
colorToPixelRGBA8 $
average [
traceRay
(cameraRay camera ((a,b) ^+^ ( 0.25/w, 0.25/h)))
2 box scene,
traceRay
traceRay
(cameraRay camera ((a,b) ^+^ (-0.25/w, 0.25/h)))
0.5 box scene,
traceRay
traceRay
(cameraRay camera ((a,b) ^+^ (0.25/w, -0.25/h)))
0.5 box scene,
traceRay
traceRay
(cameraRay camera ((a,b) ^+^ (-0.25/w,-0.25/h)))
0.5 box scene
]
where
colorToPixelRGBA8 :: Color -> PixelRGBA8
colorToPixelRGBA8 (Color rr gg bb aa) = PixelRGBA8 rr gg bb aa
instance DiscreteAproxable SymbolicObj2 [Polyline] where
discreteAprox res obj = symbolicGetContour res obj
discreteAprox = symbolicGetContour
instance DiscreteAproxable SymbolicObj2 DynamicImage where
discreteAprox _ symbObj = dynamicImage $ generateImage pixelRenderer (round w) (round h)
discreteAprox _ symbObj = ImageRGBA8 $ generateImage pixelRenderer (round w) (round h)
where
-- | Size of the image to produce.
(w,h) = (150, 150) :: 2
obj = getImplicit2 symbObj
(p1@(x1,_), p2@(_,y2)) = getBox2 symbObj
(dx, dy) = p2 ^-^ p1
dxy = max dx dy
pixelRenderer :: Int -> Int -> Color
-- | passed to generateImage, it's external, and determines this type.
pixelRenderer :: Int -> Int -> PixelRGBA8
pixelRenderer mya myb = mycolor
where
xy a b = ((x1,y2) .-^ (dxy-dx, dy-dxy)^/2) .+^ dxy*^(a/w, -b/h)
s = 0.25 ::
(a', b') = (realToFrac mya, realToFrac myb) :: (2)
mycolor = average [objColor $ xy a' b', objColor $ xy a' b',
(a', b') = (realToFrac mya, realToFrac myb) :: 2
mycolor = colorToPixelRGBA8 $ average [objColor $ xy a' b', objColor $ xy a' b',
objColor $ xy (a'+s) (b'+s),
objColor $ xy (a'-s) (b'-s),
objColor $ xy (a'+s) (b'+s),
objColor $ xy (a'-s) (b'-s)]
objColor p = if obj p < 0 then PixelRGBA8 150 150 160 255 else PixelRGBA8 255 255 255 0
colorToPixelRGBA8 :: Color -> PixelRGBA8
colorToPixelRGBA8 (Color rr gg bb aa) = PixelRGBA8 rr gg bb aa
objColor p = if obj p < 0 then Color 150 150 160 255 else Color 255 255 255 0

View File

@ -2,84 +2,68 @@
-- Copyright 2014 2015 2016, Julia Longtin (julial@turinglace.com)
-- Released under the GNU AGPLV3+, see LICENSE
-- Allow us to use explicit foralls when writing function type declarations.
{-# LANGUAGE ExplicitForAll #-}
-- export getContour, which returns as array of polylines describing the edge of a 2D object.
module Graphics.Implicit.Export.MarchingSquares (getContour) where
import Prelude(Int, Bool(True, False), ceiling, fromIntegral, (/), (+), (-), filter, map, ($), (*), (/=), (<=), (>), (.), splitAt, div, unzip, length, (++), (<), (++), head, concat, not, null, (||), Eq, Int, fst, snd)
import Prelude(Bool(True, False), ceiling, (/), (+), (-), filter, map, ($), (*), (/=), (<=), (>), splitAt, div, unzip, length, (++), (<), (++), head, ceiling, concat, div, max, not, null, (||), Eq, fromIntegral, floor)
import Graphics.Implicit.Export.Render.HandlePolylines (reducePolyline)
import Graphics.Implicit.Definitions (, , 2, both, Polyline, Obj2, (/), (*))
import Graphics.Implicit.Definitions (2, Polyline, Obj2, (/), (*))
-- FIXME: commented out for now, parallelism is not properly implemented.
-- import Control.Parallel.Strategies (using, parList, rdeepseq)
import Data.VectorSpace ((^-^), (^+^))
both :: (a -> b) -> (a,a) -> (b,b)
both f (x,y) = (f x, f y)
import Data.List(genericIndex)
-- | getContour gets a polyline describe the edge of your 2D
-- object. It's really the only function in this file you need
-- to care about from an external perspective.
import Control.Arrow((***))
-- import a helper, to clean up the result we return.
import Graphics.Implicit.Export.Render.HandlePolylines (reducePolyline)
-- Each step on the Y axis is done in parallel using Control.Parallel.Strategies
import Control.Parallel.Strategies (using, rdeepseq, parBuffer, parList)
-- getContour gets a polyline describing the edge of a 2D object.
getContour :: 2 -> 2 -> 2 -> Obj2 -> [Polyline]
getContour p1 p2 d obj =
getContour p1 p2 res obj =
let
-- How many steps will we take on each axis?
n :: (Int, Int)
n = (ceiling) `both` ((p2 ^-^ p1) / d)
nx = fst n
ny = snd n
-- Divide it up and compute the polylines
gridPos :: (Int,Int) -> (Int,Int) -> 2
gridPos (nx',ny') (mx,my) =
let
p :: 2
p = ( fromIntegral mx / fromIntegral nx'
, fromIntegral my / fromIntegral ny')
in
p1 ^+^ (p2 ^-^ p1) * p
linesOnGrid :: [[[Polyline]]]
linesOnGrid = [[getSquareLineSegs
(gridPos n (mx,my))
(gridPos n (mx+1,my+1))
obj
| mx <- [0.. nx-1] ] | my <- [0..ny-1] ]
-- Cleanup, cleanup, everybody cleanup!
-- (We connect multilines, delete redundant vertices on them, etc)
multilines = (filter polylineNotNull) $ (map reducePolyline) $ orderLinesDC $ linesOnGrid
in
multilines
-- How much space are we rendering?
d = p2 ^-^ p1
-- FIXME: Commented out, not used?
{-
getContour2 :: 2 -> 2 -> 2 -> Obj2 -> [Polyline]
getContour2 p1@(x1, y1) p2@(x2, y2) d obj =
let
-- How many steps will we take on each axis?
n@(nx,ny) = (fromIntegral . ceiling) `both` ((p2 ^-^ p1) / d)
-- Grid mapping funcs
fromGrid (mx, my) = let p = (mx/nx, my/ny)
in (p1 ^+^ (p2 ^-^ p1) / p)
toGrid (x,y) = (floor $ nx*(x-x1)/(x2-x1), floor $ ny*(y-y1)/(y2-y1))
nx ::
ny ::
n@(nx,ny) = ceiling `both` (d / res)
-- a helper for calculating a position inside of the space.
gridPos :: (,) -> (,) -> 2
gridPos n' m = p1 ^+^ d * ((fromIntegral `both` m) / (fromIntegral `both` n'))
-- alternate Grid mapping funcs
toGrid :: 2 -> (,)
toGrid f = floor `both` ((fromIntegral `both` n) * (f ^-^ p1) / d)
-- Evaluate obj on a grid, in parallel.
valsOnGrid :: [[]]
valsOnGrid = [[ obj (fromGrid (mx, my)) | mx <- [0.. nx-1] ] | my <- [0..ny-1] ]
`using` parList rdeepseq
valsOnGrid = [[ obj $ gridPos n (mx, my) | mx <- [0..nx-1] ] | my <- [0..ny-1] ] `using` parList rdeepseq
-- A faster version of the obj. Sort of like memoization, but done in advance, in parallel.
preEvaledObj p = valsOnGrid !! my !! mx where (mx,my) = toGrid p
-- Divide it up and compute the polylines
preEvaledObj p = valsOnGrid `genericIndex` my `genericIndex` mx where (mx,my) = toGrid p
-- compute the polylines
linesOnGrid :: [[[Polyline]]]
linesOnGrid = [[getSquareLineSegs (fromGrid (mx, my)) (fromGrid (mx+1, my+1)) preEvaledObj
| mx <- [0.. nx-1] ] | my <- [0..ny-1] ]
linesOnGrid = [[getSquareLineSegs (gridPos n (mx, my)) (gridPos n (mx+1, my+1)) preEvaledObj
| mx <- [0.. nx-1] ] | my <- [0..ny-1] ] `using` parBuffer (max 1 $ fromIntegral $ div ny 32) rdeepseq
-- Cleanup, cleanup, everybody cleanup!
-- (We connect multilines, delete redundant vertices on them, etc)
multilines = (filter polylineNotNull) $ (map reducePolyline) $ orderLinesDC $ linesOnGrid
lines = filter polylineNotNull $ map reducePolyline $ orderLinesDC linesOnGrid
in
multilines
-}
lines
-- | This function gives line segments to divide negative interior
-- regions and positive exterior ones inside a square, based on its
-- regions and positive exterior ones inside a square, based on the
-- values at its vertices.
-- It is based on the linearly-interpolated marching squares algorithm.
@ -88,11 +72,13 @@ getSquareLineSegs (x1, y1) (x2, y2) obj =
let
(x,y) = (x1, y1)
-- Let's evlauate obj at a few points...
-- Let's evlauate obj at four corners...
x1y1 = obj (x1, y1)
x2y1 = obj (x2, y1)
x1y2 = obj (x1, y2)
x2y2 = obj (x2, y2)
-- And the center point..
c = obj ((x1+x2)/2, (y1+y2)/2)
dx = x2 - x1
@ -111,17 +97,19 @@ getSquareLineSegs (x1, y1) (x2, y2) obj =
-- ---------*----------
-- midy1
midx1 = (x, y + dy*x1y1/(x1y1-x1y2))
midx2 = (x + dx, y + dy*x2y1/(x2y1-x2y2))
midy1 = (x + dx*x1y1/(x1y1-x2y1), y )
midy2 = (x + dx*x1y2/(x1y2-x2y2), y + dy)
notPointLine :: Eq a => [a] -> Bool
notPointLine (p1:p2:[]) = p1 /= p2
notPointLine ([]) = False
notPointLine ([_]) = False
notPointLine (_ : (_ : (_ : _))) = False
in filter (notPointLine) $ case (x1y2 <= 0, x2y2 <= 0,
x1y1 <= 0, x2y1 <= 0) of
notPointLine (start:stop:xs) = start /= stop || notPointLine [stop:xs]
notPointLine [_] = False
notPointLine [] = False
in filter notPointLine $ case (x1y2 <= 0, x2y2 <= 0,
x1y1 <= 0, x2y1 <= 0) of
-- Yes, there's some symetries that could reduce the amount of code...
-- But I don't think they're worth exploiting...
(True, True,
@ -162,11 +150,22 @@ getSquareLineSegs (x1, y1) (x2, y2) obj =
else [[midx1, midy1], [midx2, midy2]]
-- $ Functions for cleaning up the polylines
-- Functions for cleaning up the polylines.
-- Many have multiple implementations as efficiency experiments.
-- At some point, we'll get rid of the redundant ones....
-- FIXME: document the algorithm this uses better.
orderLinesDC :: [[[Polyline]]] -> [Polyline]
orderLinesDC segs =
let
halve :: [a] -> ([a], [a])
halve l = splitAt (div (length l) 2) l
splitOrder segs' = case (halve *** halve) $ unzip $ map halve $ segs' of
((a,b),(c,d)) -> orderLinesDC a ++ orderLinesDC b ++ orderLinesDC c ++ orderLinesDC d
in
if length segs < 5 || length (head segs) < 5 then concat $ concat segs else
splitOrder segs
{-
orderLines :: [Polyline] -> [Polyline]
orderLines [] = []
@ -182,16 +181,6 @@ orderLines (present:remaining) =
(Just match, others) -> orderLines $ (present ++ tail match): others
-}
orderLinesDC :: [[[Polyline]]] -> [Polyline]
orderLinesDC segs =
let
halve :: [a] -> ([a], [a])
halve l = splitAt (div (length l) 2) l
splitOrder segs' = case (\(x,y) -> (halve x, halve y)) . unzip . map (halve) $ segs' of
((a,b),(c,d)) -> orderLinesDC a ++ orderLinesDC b ++ orderLinesDC c ++ orderLinesDC d
in
if (length segs < 5 || length (head segs) < 5 ) then concat $ concat segs else
splitOrder segs
{-
orderLinesP :: [[[Polyline]]] -> [Polyline]
orderLinesP segs =

View File

@ -5,31 +5,54 @@
-- Allow us to use explicit foralls when writing function type declarations.
{-# LANGUAGE ExplicitForAll #-}
-- define getContour, which gets a polyline describe the edge of your 2D object.
-- export getContourMesh, which returns an array of triangles describing the interior of a 2D object.
module Graphics.Implicit.Export.MarchingSquaresFill (getContourMesh) where
import Prelude(Bool(True, False), fromInteger, ($), (-), (+), (/), (*), (<=), (>), ceiling, concat)
import Prelude(Bool(True, False), fromIntegral, ($), (-), (+), (/), (*), (<=), (>), ceiling, concat, max, div, floor)
import Graphics.Implicit.Definitions (, 2, Obj2)
import Graphics.Implicit.Definitions (, , 2, Polytri(Polytri), Obj2, (/), (*))
-- FIXME: commented out, test how to apply..
-- import Control.Parallel (par, pseq)
import Data.VectorSpace ((^-^),(^+^))
getContourMesh :: 2 -> 2 -> 2 -> Obj2 -> [(2,2,2)]
getContourMesh (x1, y1) (x2, y2) (dx, dy) obj =
import Data.List(genericIndex)
-- Each step on the Y axis is done in parallel using Control.Parallel.Strategies
import Control.Parallel.Strategies (using, rdeepseq, parBuffer, parList)
-- apply a function to both items in the provided tuple.
both :: forall t b. (t -> b) -> (t, t) -> (b, b)
both f (x,y) = (f x, f y)
getContourMesh :: 2 -> 2 -> 2 -> Obj2 -> [Polytri]
getContourMesh p1 p2 res obj =
let
-- How much space are we rendering?
d = p2 ^-^ p1
-- How many steps will we take on each axis?
nx ::
nx = fromInteger $ ceiling $ (x2 - x1) / dx
ny ::
ny = fromInteger $ ceiling $ (y2 - y1) / dy
-- Divide it up and compute the polylines
trisOnGrid :: [[[(2,2,2)]]]
trisOnGrid = [[getSquareTriangles
(x1 + (x2 - x1)*mx/nx, y1 + (y2 - y1)*my/ny)
(x1 + (x2 - x1)*(mx+1)/nx, y1 + (y2 - y1)*(my+1)/ny)
obj
| mx <- [0.. nx-1] ] | my <- [0..ny-1] ]
nx ::
ny ::
n@(nx,ny) = ceiling `both` (d / res)
-- a helper for calculating a position inside of the space.
gridPos :: (,) -> (,) -> 2
gridPos n' m = p1 ^+^ d * ((fromIntegral `both` m) / (fromIntegral `both` n'))
-- alternate Grid mapping funcs
toGrid :: 2 -> (,)
toGrid f = floor `both` ((fromIntegral `both` n) * (f ^-^ p1) / d)
-- Evaluate obj on a grid, in parallel.
valsOnGrid :: [[]]
valsOnGrid = [[ obj $ gridPos n (mx, my) | mx <- [0..nx-1] ] | my <- [0..ny-1] ] `using` parList rdeepseq
-- A faster version of the obj. Sort of like memoization, but done in advance, in parallel.
preEvaledObj p = valsOnGrid `genericIndex` my `genericIndex` mx where (mx,my) = toGrid p
-- compute the triangles.
trisOnGrid :: [[[Polytri]]]
trisOnGrid = [[getSquareTriangles (gridPos n (mx,my)) (gridPos n (mx+1,my+1)) preEvaledObj
| mx <- [0.. nx-1] ] | my <- [0..ny-1] ] `using` parBuffer (max 1 $ fromIntegral $ div ny 32) rdeepseq
triangles = concat $ concat trisOnGrid
in
triangles
@ -39,16 +62,18 @@ getContourMesh (x1, y1) (x2, y2) (dx, dy) obj =
-- values at its vertices.
-- It is based on the linearly-interpolated marching squares algorithm.
getSquareTriangles :: 2 -> 2 -> Obj2 -> [(2,2,2)]
getSquareTriangles :: 2 -> 2 -> Obj2 -> [Polytri]
getSquareTriangles (x1, y1) (x2, y2) obj =
let
(x,y) = (x1, y1)
-- Let's evlauate obj at a few points...
-- Let's evaluate obj at four corners...
x1y1 = obj (x1, y1)
x2y1 = obj (x2, y1)
x1y2 = obj (x1, y2)
x2y2 = obj (x2, y2)
-- And the center point..
c = obj ((x1+x2)/2, (y1+y2)/2)
dx = x2 - x1
@ -56,16 +81,16 @@ getSquareTriangles (x1, y1) (x2, y2) obj =
-- linearly interpolated midpoints on the relevant axis
-- midy2
-- _________*__________
-- | |
-- | |
-- | |
--midx1* * midx2
-- | |
-- | |
-- | |
-- -----------*----------
-- midy1
-- _________*_________
-- | |
-- | |
-- | |
--midx1* * midx2
-- | |
-- | |
-- | |
-- ---------*---------
-- midy1
midx1 = (x, y + dy*x1y1/(x1y1-x1y2))
midx2 = (x + dx, y + dy*x2y1/(x2y1-x2y2))
@ -73,8 +98,8 @@ getSquareTriangles (x1, y1) (x2, y2) obj =
midy2 = (x + dx*x1y2/(x1y2-x2y2), y + dy)
-- decompose a square into two triangles...
square :: forall t t1. t -> t1 -> t1 -> t1 -> [(t, t1, t1)]
square aa bb cc dd = [(aa,bb,cc), (aa,cc,dd)]
square :: 2 -> 2 -> 2 -> 2 -> [Polytri]
square aa bb cc dd = [Polytri (aa,bb,cc), Polytri (aa,cc,dd)]
in case (x1y2 <= 0, x2y2 <= 0,
x1y1 <= 0, x2y1 <= 0) of
@ -93,33 +118,30 @@ getSquareTriangles (x1, y1) (x2, y2) obj =
(True, False,
True, False) -> square (x1,y1) midy1 midy2 (x1,y2)
(True, False,
False, False) -> [((x1,y2), midx1, midy2)]
False, False) -> [Polytri ((x1,y2), midx1, midy2)]
(False, True,
True, True) ->
[(midx1, (x1,y1), midy2), ((x1,y1), (x2,y1), midy2), (midy2, (x2,y1), (x2,y2))]
[Polytri (midx1, (x1,y1), midy2), Polytri ((x1,y1), (x2,y1), midy2), Polytri (midy2, (x2,y1), (x2,y2))]
(True, True,
False, True) ->
[((x1,y2), midx1, (x2,y2)), (midx1, midy1, (x2,y2)), ((x2,y2), midy1, (x2,y1))]
[Polytri ((x1,y2), midx1, (x2,y2)), Polytri (midx1, midy1, (x2,y2)), Polytri ((x2,y2), midy1, (x2,y1))]
(False, False,
True, False) -> [(midx1, (x1,y1), midy1)]
True, False) -> [Polytri (midx1, (x1,y1), midy1)]
(True, True,
True, False) ->
[(midy1,midx2,(x2,y2)), ((x2,y2), (x1,y2), midy1), (midy1, (x1,y2), (x1,y1))]
[Polytri (midy1,midx2,(x2,y2)), Polytri ((x2,y2), (x1,y2), midy1), Polytri (midy1, (x1,y2), (x1,y1))]
(False, False,
False, True) -> [(midx2, midy1, (x2,y1))]
False, True) -> [Polytri (midx2, midy1, (x2,y1))]
(True, False,
True, True) ->
[(midy2, (x2,y1), midx2), ((x2,y1), midy2, (x1,y1)), ((x1,y1), midy2, (x1,y2))]
[Polytri (midy2, (x2,y1), midx2), Polytri ((x2,y1), midy2, (x1,y1)), Polytri ((x1,y1), midy2, (x1,y2))]
(False, True,
False, False) -> [(midx2, (x2,y2), midy2)]
False, False) -> [Polytri (midx2, (x2,y2), midy2)]
(True, False,
False, True) -> if c > 0
then [((x1,y2), midx1, midy2), ((x2,y1), midy1, midx2)]
then [Polytri ((x1,y2), midx1, midy2), Polytri ((x2,y1), midy1, midx2)] --[[midx1, midy2], [midx2, midy1]]
else [] --[[midx1, midy1], [midx2, midy2]]
(False, True,
True, False) -> if c <= 0
then [] --[[midx1, midy2], [midx2, midy1]]
else [((x1,y1), midy1, midx1), ((x2,y2), midx2, midy2)] --[[midx1, midy1], [midx2, midy2]]
else [Polytri ((x1,y1), midy1, midx1), Polytri ((x2,y2), midx2, midy2)] --[[midx1, midy1], [midx2, midy2]]

View File

@ -9,29 +9,29 @@ module Graphics.Implicit.Export.NormedTriangleMeshFormats (obj) where
import Prelude(($), map, (+), (.), (*), length, (-), return)
import Graphics.Implicit.Definitions (NormedTriangle, 3)
import Graphics.Implicit.Definitions (NormedTriangle(NormedTriangle), NormedTriangleMesh(NormedTriangleMesh), 3)
import Graphics.Implicit.Export.TextBuilderUtils (Text, Builder, toLazyText, (<>), bf, mconcat, buildInt)
obj :: [NormedTriangle] -> Text
obj normedtriangles = toLazyText $ vertcode <> normcode <> trianglecode
-- | Generate a .obformat file from a NormedTriangleMesh
obj :: NormedTriangleMesh -> Text
obj (NormedTriangleMesh normedtriangles) = toLazyText $ vertcode <> normcode <> trianglecode
where
-- A vertex line; v (0.0, 0.0, 1.0) = "v 0.0 0.0 1.0\n"
-- | A vertex line; v (0.0, 0.0, 1.0) = "v 0.0 0.0 1.0\n"
v :: 3 -> Builder
v (x,y,z) = "v " <> bf x <> " " <> bf y <> " " <> bf z <> "\n"
-- A normal line; n (0.0, 0.0, 1.0) = "vn 0.0 0.0 1.0\n"
-- | A normal line; n (0.0, 0.0, 1.0) = "vn 0.0 0.0 1.0\n"
n :: 3 -> Builder
n (x,y,z) = "vn " <> bf x <> " " <> bf y <> " " <> bf z <> "\n"
verts = do
-- extract the vertices for each triangle
-- recall that a normed triangle is of the form ((vert, norm), ...)
((a,_),(b,_),(c,_)) <- normedtriangles
-- The vertices from each triangle take up 3 position in the resulting list
-- | Extract the vertices for each triangle
-- recall that a normed triangle is of the form ((vert, norm), ...)
NormedTriangle ((a,_),(b,_),(c,_)) <- normedtriangles
-- | The vertices from each triangle take up 3 position in the resulting list
[a,b,c]
norms = do
-- extract the normals for each triangle
((_,a),(_,b),(_,c)) <- normedtriangles
-- The normals from each triangle take up 3 position in the resulting list
-- | extract the normals for each triangle
NormedTriangle ((_,a),(_,b),(_,c)) <- normedtriangles
-- | The normals from each triangle take up 3 position in the resulting list
[a,b,c]
vertcode = mconcat $ map v verts
normcode = mconcat $ map n norms

View File

@ -7,63 +7,142 @@
{-# LANGUAGE OverloadedStrings #-}
module Graphics.Implicit.Export.PolylineFormats where
module Graphics.Implicit.Export.PolylineFormats (svg, hacklabLaserGCode, dxf2) where
import Prelude((.), ($), (-), (+), (/), minimum, maximum, unzip, concat, show, (++), unwords, map, mapM_, snd, compare, min, max, Ord, Num)
import Prelude((.), ($), (-), (+), (/), minimum, maximum, unzip, show, (++), unwords, map, mapM_, snd, compare, min, max, length, concat, foldl)
import Graphics.Implicit.Definitions (Polyline, 2)
import Graphics.Implicit.Definitions (Polyline(Polyline), , 2)
import Graphics.Implicit.Export.TextBuilderUtils (Text, Builder, mempty, toLazyText, mconcat, bf, (<>), buildTruncFloat)
import Graphics.Implicit.Export.TextBuilderUtils (Text, Builder, mempty, toLazyText, mconcat, bf, (<>), buildInt, buildTruncFloat)
import Text.Blaze.Svg.Renderer.Text (renderSvg)
import Text.Blaze.Svg11 ((!),docTypeSvg,g,polyline,toValue)
import Text.Blaze.Svg11 ((!),docTypeSvg,g,polyline,toValue,Svg)
import Text.Blaze.Internal (stringValue)
import qualified Text.Blaze.Svg11.Attributes as A
import qualified Text.Blaze.Svg11.Attributes as A (version, width, height, viewbox, points, stroke, strokeWidth, fill)
import qualified Data.List as List
import Data.List (sortBy)
default ()
-- FIXME: magic numbers.
svg :: [Polyline] -> Text
svg plines = renderSvg . svg11 . svg' $ plines
where
strokeWidth = 1.0
(xmin, xmax, ymin, ymax) = ((minimum xs) - margin, (maximum xs) + margin, (minimum ys) - margin, (maximum ys) + margin)
strokeWidth ::
strokeWidth = 1
(xmin, xmax, ymin, ymax) = (xmin' - margin, xmax' + margin, ymin' - margin, ymax' + margin)
where margin = strokeWidth / 2
(xs,ys) = unzip (concat plines)
svg11 content = docTypeSvg ! A.version "1.1"
! A.width (stringValue $ show (xmax-xmin) ++ "mm")
! A.height (stringValue $ show (ymax-ymin) ++ "mm")
! A.viewbox (stringValue $ unwords . map show $ [0,0,xmax-xmin,ymax-ymin])
$ content
((xmin', xmax'), (ymin', ymax')) = (maxMinList xs,maxMinList ys)
(xs,ys) = unzip $ concat $ map pair plines
pair (Polyline a) = a
maxMinList :: [] -> (,)
maxMinList (x:others) = foldl (\(l,h) y -> (min l y, max h y)) (x,x) others
maxMinList [] = (0,0)
svg11 = docTypeSvg ! A.version "1.1"
! A.width (stringValue $ show (xmax-xmin) ++ "mm")
! A.height (stringValue $ show (ymax-ymin) ++ "mm")
! A.viewbox (stringValue $ unwords . map show $ [0,0,xmax-xmin,ymax-ymin])
-- The reason this isn't totally straightforwards is that svg has different coordinate system
-- and we need to compute the requisite translation.
svg' [] = mempty
svg' :: [Polyline] -> Svg
svg' [] = mempty
-- When we have a known point, we can compute said transformation:
svg' polylines = thinBlueGroup $ mapM_ poly polylines
poly line = polyline ! A.points pointList
poly (Polyline line) = polyline ! A.points pointList
where pointList = toValue $ toLazyText $ mconcat [bf (x-xmin) <> "," <> bf (ymax - y) <> " " | (x,y) <- line]
-- Instead of setting styles on every polyline, we wrap the lines in a group element and set the styles on it:
thinBlueGroup = g ! A.stroke "rgb(0,0,255)" ! A.strokeWidth (stringValue $ show strokeWidth) ! A.fill "none" -- obj
-- DXF2 export in 2D. conforming to AutoCAD R12/13.
dxf2 :: [Polyline] -> Text
dxf2 plines = toLazyText $ dxf2Header <> dxf2Tables <> dxf2Blocks <> dxf2Entities
where
dxf2Header :: Builder
dxf2Header = mconcat [
" 0\n", "SECTION\n",
" 2\n", "HEADER\n",
" 9\n", "$ACADVER\n",
" 1\n", "AC1009\n",
" 9\n", "$LIMMIN\n",
" 10\n", buildTruncFloat dxfxmin, "\n",
" 20\n", buildTruncFloat dxfymin, "\n",
" 9\n", "$LIMMAX\n",
" 10\n", buildTruncFloat dxfxmax, "\n",
" 20\n", buildTruncFloat dxfymax, "\n",
" 9\n", "$LUPREC\n",
" 70\n", "4\n",
" 0\n", "ENDSEC\n"
]
dxf2Tables :: Builder
dxf2Tables = mconcat [
" 0\n", "SECTION\n",
" 2\n", "TABLES\n",
" 0\n", "ENDSEC\n"
]
dxf2Blocks :: Builder
dxf2Blocks = mconcat [
" 0\n", "SECTION\n",
" 2\n", "BLOCKS\n",
" 0\n", "ENDSEC\n"
]
dxf2Entities :: Builder
dxf2Entities = mconcat [
" 0\n", "SECTION\n",
" 2\n", "ENTITIES\n",
mconcat [ buildPolyline orderedPolyline | orderedPolyline <- orderPolylines plines],
" 0\n", "ENDSEC\n"
]
buildPolyline :: Polyline -> Builder
buildPolyline (Polyline singlePolyline) =
mconcat [
" 0\n", "POLYLINE\n",
" 8\n", "0\n",
" 6\n", "CONTINUOUS\n",
" 66\n", "1\n",
" 62\n", buildInt $ length singlePolyline,"\n",
" 10\n", "0.0\n",
" 20\n", "0.0\n",
" 30\n", "0.0000\n",
mconcat [ buildVertex vertex | vertex <- singlePolyline ],
" 0\n", "SEQEND\n"
]
buildVertex :: (2) -> Builder
buildVertex (x1,y1) =
mconcat [
" 0\n", "VERTEX\n",
" 8\n", "0\n",
" 10\n", buildTruncFloat x1, "\n",
" 20\n", buildTruncFloat y1, "\n"
]
(dxfxmin, dxfxmax, dxfymin, dxfymax) = (minimum xs, maximum xs, minimum ys, maximum ys)
(xs, ys) = unzip $ concat $ map pair plines
pair :: Polyline -> [2]
pair (Polyline x) = x
orderPolylines :: [Polyline] -> [Polyline]
orderPolylines plines =
map snd . sortBy (\(a,_) (b, _) -> compare a b) . map (\x -> (polylineRadius x, x)) $ plines
where
polylineRadius :: Polyline ->
polylineRadius polyline' = max (xmax' - xmin') (ymax' - ymin')
where
((xmin', xmax'), (ymin', ymax')) = polylineRadius' [polyline']
polylineRadius' :: [Polyline] -> (2, 2)
polylineRadius' lines = (maxMinList xs,maxMinList ys)
where
(xs,ys) = unzip $ concat $ map pair lines
pair (Polyline a) = a
maxMinList :: [] -> (,)
maxMinList (x:others) = foldl (\(l,h) y -> (min l y, max h y)) (x,x) others
maxMinList [] = (0,0)
-- Gcode generation for the laser cutter in HackLab. Complies with https://ws680.nist.gov/publication/get_pdf.cfm?pub_id=823374
hacklabLaserGCode :: [Polyline] -> Text
hacklabLaserGCode polylines = toLazyText $ gcodeHeader <> mconcat (map interpretPolyline orderedPolylines) <> gcodeFooter
where
orderedPolylines =
snd . unzip
. List.sortBy (\(a,_) (b, _) -> compare a b)
. map (\x -> (polylineRadius x, x))
$ polylines
polylineRadius :: forall t. (Ord t, Num t) => [(t, t)] -> t
polylineRadius [] = 0
polylineRadius polyline' = max (xmax' - xmin') (ymax' - ymin') where
((xmin', xmax'), (ymin', ymax')) = polylineRadius' polyline'
polylineRadius' :: forall a a1. (Ord a1, Ord a, Num a1, Num a) => [(a, a1)] -> ((a, a), (a1, a1))
polylineRadius' [] = ((0,0),(0,0))
polylineRadius' [(x,y)] = ((x,x),(y,y))
polylineRadius' ((x,y):ps) = ((min x xmin,max x xmax),(min y ymin, max y ymax))
where ((xmin, xmax), (ymin, ymax)) = polylineRadius' ps
hacklabLaserGCode polylines = toLazyText $ gcodeHeader <> mconcat (map interpretPolyline (orderPolylines polylines)) <> gcodeFooter
where
gcodeHeader :: Builder
gcodeHeader = mconcat [
"(generated by ImplicitCAD, based of hacklab wiki example)\n"
@ -79,11 +158,11 @@ hacklabLaserGCode polylines = toLazyText $ gcodeHeader <> mconcat (map interpret
,"M2 (end)"]
gcodeXY :: 2 -> Builder
gcodeXY (x,y) = mconcat ["X", buildTruncFloat x, " Y", buildTruncFloat y]
interpretPolyline (start:others) = mconcat [
interpretPolyline :: Polyline -> Builder
interpretPolyline (Polyline (start:others)) = mconcat [
"G00 ", gcodeXY start
,"\nM62 P0 (laser on)\n"
,mconcat [ "G01 " <> gcodeXY point <> "\n" | point <- others]
,"M63 P0 (laser off)\n\n"
]
interpretPolyline [] = mempty
interpretPolyline (Polyline []) = mempty

View File

@ -2,65 +2,71 @@
-- Copyright 2016, Julia Longtin (julial@turinglace.com)
-- Released under the GNU AGPLV3+, see LICENSE
-- Allow us to use explicit foralls when writing function type declarations.
{-# LANGUAGE ExplicitForAll #-}
-- FIXME: why are these needed?
{-# LANGUAGE TypeSynonymInstances, MultiParamTypeClasses, FlexibleContexts #-}
module Graphics.Implicit.Export.RayTrace where
module Graphics.Implicit.Export.RayTrace( Color(Color), average, Camera(Camera), Light(Light), Scene(Scene), traceRay, cameraRay) where
import Prelude(Show, RealFrac, Maybe(Just, Nothing), Int, Bool(False, True), (-), (.), ($), (*), (/), min, fromInteger, max, round, fromIntegral, unzip, map, length, sum, maximum, minimum, (>), (+), (<), (==), pred, flip, (++), not, abs, floor, fromIntegral, toRational)
import Prelude(Show, RealFrac, Maybe(Just, Nothing), Bool(False, True), (-), (.), ($), (*), (/), min, fromInteger, max, round, fromIntegral, unzip, map, length, sum, maximum, minimum, (>), (+), (<), (==), pred, flip, not, abs, floor, toRational, otherwise)
import Graphics.Implicit.Definitions (, , 2, 3, (), Obj3)
import Codec.Picture (Pixel8)
import Graphics.Implicit.Definitions (, 2, 3, (), Obj3)
import Codec.Picture (Pixel8, Image, DynamicImage(ImageRGBA8), PixelRGBA8(PixelRGBA8))
import Control.Monad (guard, return)
import Control.Arrow ((***))
import Data.VectorSpace (Scalar, magnitude, (^+^), (*^), normalized, (^-^), InnerSpace)
import Data.Cross (cross3)
default (, )
-- Definitions
data Camera = Camera 3 3 3
deriving Show
-- | A ray. A point, and a normal pointing in the direction the ray is going.
data Ray = Ray 3 3
deriving Show
data Light = Light 3
deriving Show
data Scene = Scene Obj3 Color [Light] Color
type Color = PixelRGBA8
-- | A light source. source point, and intensity.
data Light = Light 3
deriving Show
color :: Pixel8 -> Pixel8 -> Pixel8 -> Pixel8 -> PixelRGBA8
color r g b a = PixelRGBA8 r g b a
dynamicImage :: Image PixelRGBA8 -> DynamicImage
dynamicImage = ImageRGBA8
-- | A colour. Red Green Blue and Alpha components.
data Color = Color Pixel8 Pixel8 Pixel8 Pixel8
-- Math
-- | The distance traveled by a line segment from the first point to the second point.
vectorDistance :: 3 -> 3 -> Scalar 3
vectorDistance a b = magnitude (b-a)
colorMult :: Pixel8 -> PixelRGBA8 -> PixelRGBA8
s `colorMult` (PixelRGBA8 a b c d) = color (s `mult` a) (s `mult` b) (s `mult` c) d
-- | Multiply a colour by an intensity.
colorMult :: Pixel8 -> Color -> Color
s `colorMult` (Color a b c d) = Color (s `mult` a) (s `mult` b) (s `mult` c) d
where
bound :: RealFrac a => a -> a
bound = max 0 . min 254
mult :: Pixel8 -> Pixel8 -> Pixel8
mult x y = round . bound . toRational $ x * y
-- | Average a set of colours.
average :: [Color] -> Color
average l =
let
((rs, gs), (bs, as)) = (\(a'',b'') -> (unzip a'', unzip b'')) $ unzip $ map
(\(PixelRGBA8 r g b a) -> ((fromIntegral r, fromIntegral g), (fromIntegral b, fromIntegral a)))
l :: (([], []), ([],[]))
n = fromIntegral $ length l ::
average l =
let
((rs, gs), (bs, as)) = (unzip *** unzip) . unzip $ map
(\(Color r g b a) -> ((fromIntegral r, fromIntegral g), (fromIntegral b, fromIntegral a)))
l :: (([], []), ([], []))
n ::
n = fromIntegral $ length l
(r', g', b', a') = (sum rs/n, sum gs/n, sum bs/n, sum as/n)
in PixelRGBA8
in Color
(fromInteger . round $ r') (fromInteger . round $ g') (fromInteger . round $ b') (fromInteger . round $ a')
-- Ray Utilities
@ -74,6 +80,7 @@ cameraRay (Camera p vx vy f) (x,y) =
in
Ray p' n
-- | Create a ray from two points.
rayFromTo :: 3 -> 3 -> Ray
rayFromTo p1 p2 = Ray p1 (normalized $ p2 ^-^ p1)
@ -91,19 +98,16 @@ rayBounds ray box =
(lower, upper)
-- Intersection
intersection :: Ray -> ((,), ) -> -> Obj3 -> Maybe 3
intersection r@(Ray p v) ((a, aval),b) res obj =
let
step =
if aval/(4::) > res then res
else if aval/(2::) > res then res/(2 :: )
else res/(10 :: )
step | aval/4 > res = res
| aval/2 > res = res/2
| otherwise = res/10
a' = a + step
a'val = obj (p ^+^ a'*^v)
in if a'val < 0
then
then
let a'' = refine (a,a') (\s -> obj (p ^+^ s*^v))
in Just (p ^+^ a''*^v)
else if a' < b
@ -111,18 +115,18 @@ intersection r@(Ray p v) ((a, aval),b) res obj =
else Nothing
refine :: 2 -> ( -> ) ->
refine (a, b) obj =
refine (a, b) obj =
let
(aval, bval) = (obj a, obj b)
in if bval < aval
then refine' 10 (a, b) (aval, bval) obj
else refine' 10 (b, a) (aval, bval) obj
refine' :: Int -> 2 -> 2 -> ( -> ) ->
refine' :: -> 2 -> 2 -> ( -> ) ->
refine' 0 (a, _) _ _ = a
refine' n (a, b) (aval, bval) obj =
refine' n (a, b) (aval, bval) obj =
let
mid = (a+b)/(2::)
mid = (a+b)/2
midval = obj mid
in
if midval == 0
@ -137,13 +141,12 @@ intersects a b c d = case intersection a b c d of
Just _ -> True
-- Trace
traceRay :: Ray -> -> (3, 3) -> Scene -> Color
traceRay ray@(Ray cameraP cameraV) step box (Scene obj objColor lights defaultColor) =
let
(a,b) = rayBounds ray box
in case intersection ray ((a, obj (cameraP ^+^ a*^cameraV)), b) step obj of
Just p -> flip colorMult objColor $ floor (sum $ [0.2] ++ do
Just p -> flip colorMult objColor $ floor (sum $ 0.2 : do
Light lightPos lightIntensity <- lights
let
ray'@(Ray _ v) = rayFromTo p lightPos
@ -154,19 +157,19 @@ traceRay ray@(Ray cameraP cameraV) step box (Scene obj objColor lights defaultCo
dirDeriv :: 3 ->
dirDeriv v'' = (obj (p ^+^ step*^v'') ^-^ pval)/step
deriv = (dirDeriv (1,0,0), dirDeriv (0,1,0), dirDeriv (0,0,1))
normal = normalized $ deriv
unitV = normalized $ v'
proj :: forall v. InnerSpace v => v -> v -> v
normal = normalized deriv
unitV = normalized v'
proj :: InnerSpace v => v -> v -> v
proj a' b' = (a'b')*^b'
dist = vectorDistance p lightPos
illumination = (max 0 (normal unitV)) * lightIntensity * (25 /dist)
rV =
illumination = max 0 (normal unitV) * lightIntensity * (25 /dist)
rV =
let
normalComponent = proj v' normal
parComponent = v' - normalComponent
in
normalComponent - parComponent
return $ illumination*(3 + 0.3*(abs $ rV cameraV)*(abs $ rV cameraV))
normalComponent - parComponent
return $ illumination*(3 + 0.3*abs(rV cameraV)*abs(rV cameraV))
)
Nothing -> defaultColor

View File

@ -8,11 +8,12 @@
-- Allow us to use the tearser parallel list comprehension syntax, to avoid having to call zip in the complicated comprehensions below.
{-# LANGUAGE ParallelListComp #-}
module Graphics.Implicit.Export.Render where
-- export getContour and getMesh, which returns the edge of a 2D object, or the surface of a 3D object, respectively.
module Graphics.Implicit.Export.Render (getMesh, getContour) where
import Prelude(Float, Bool, ceiling, ($), (/), fromIntegral, (+), (*), fromInteger, max, div, tail, map, concat, realToFrac, (==), (||), filter, not, reverse, (.), Integral, Eq, Integer, concatMap)
import Prelude(ceiling, ($), fromIntegral, (+), (*), max, div, tail, map, concat, reverse, (.), concatMap)
import Graphics.Implicit.Definitions (, 2, 3, Obj2, Obj3, TriangleMesh, Triangle, Polyline)
import Graphics.Implicit.Definitions (, , 2, 3, TriangleMesh, Obj2, Obj3, Polyline(Polyline), (/), both, allthree, fromto)
import Data.VectorSpace ((^-^))
@ -41,7 +42,7 @@ import Graphics.Implicit.Export.Render.HandleSquares (mergedSquareTris)
-- Success: This is our mesh.
-- Each step is done in parallel using Control.Parallel.Strategies
-- Each step on the Z axis is done in parallel using Control.Parallel.Strategies
import Control.Parallel.Strategies (using, rdeepseq, parBuffer)
import Control.DeepSeq (NFData)
@ -66,38 +67,36 @@ import Control.DeepSeq (NFData)
-- For the 2D case, we need one last thing, cleanLoopsFromSegs:
import Graphics.Implicit.Export.Render.HandlePolylines (cleanLoopsFromSegs)
-- FIXME: res should be 3, not .
getMesh :: 3 -> 3 -> -> Obj3 -> TriangleMesh
getMesh p1@(x1,y1,z1) p2 res obj =
let
-- How much space are we rendering?
(dx,dy,dz) = p2 ^-^ p1
d = p2 ^-^ p1
-- How many steps will we take on each axis?
nx :: Integral a => a
nx = ceiling $ dx / res
ny :: Integral a => a
ny = ceiling $ dy / res
nz :: Integral a => a
nz = ceiling $ dz / res
nx ::
ny ::
nz ::
(nx,ny,nz) = ceiling `allthree` ( d / (res,res,res))
-- How big are the steps?
rx = dx / fromInteger nx
ry = dy / fromInteger ny
rz = dz / fromInteger nz
(rx,ry,rz) = d / (fromto `allthree` (nx,ny,nz))
-- The positions we're rendering.
pXs = [ x1 + rx*n | n <- [0.. fromInteger nx] ]
pYs = [ y1 + ry*n | n <- [0.. fromInteger ny] ]
pZs = [ z1 + rz*n | n <- [0.. fromInteger nz] ]
pXs = [ x1 + rx*n | n <- [0.. fromto nx] ]
pYs = [ y1 + ry*n | n <- [0.. fromto ny] ]
pZs = [ z1 + rz*n | n <- [0.. fromto nz] ]
par3DList :: forall t. NFData t => Integer -> Integer -> Integer -> ((Integer -> ) -> Integer -> (Integer -> ) -> Integer -> (Integer -> ) -> Integer -> t) -> [[[t]]]
-- | Perform a function on every point in a 3D grid.
par3DList :: forall t. NFData t => -> -> -> (( -> ) -> -> ( -> ) -> -> ( -> ) -> -> t) -> [[[t]]]
par3DList lenx leny lenz f =
[[[f
(\n -> x1 + rx*fromInteger (mx+n)) mx
(\n -> y1 + ry*fromInteger (my+n)) my
(\n -> z1 + rz*fromInteger (mz+n)) mz
(\n -> x1 + rx*fromto (mx+n)) mx
(\n -> y1 + ry*fromto (my+n)) my
(\n -> z1 + rz*fromto (mz+n)) mz
| mx <- [0..lenx] ] | my <- [0..leny] ] | mz <- [0..lenz] ]
`using` (parBuffer (max 1 . fromInteger $ div lenz 32) rdeepseq)
`using` parBuffer (max 1 . fromIntegral $ div lenz 32) rdeepseq
-- Evaluate obj to avoid waste in mids, segs, later.
objV = par3DList (nx+2) (ny+2) (nz+2) $ \x _ y _ z _ -> obj (x 0, y 0, z 0)
@ -108,25 +107,25 @@ getMesh p1@(x1,y1,z1) p2 res obj =
| x0 <- pXs | objX0Y0Z0 <- objY0Z0 | objX0Y0Z1 <- objY0Z1
]| y0 <- pYs | objY0Z0 <- objZ0 | objY0Z1 <- objZ1
]| z0 <- pZs | z1' <- tail pZs | objZ0 <- objV | objZ1 <- tail objV
] `using` (parBuffer (max 1 . fromInteger $ div nz 32) rdeepseq)
] `using` parBuffer (max 1 . fromIntegral $ div nz 32) rdeepseq
midsY = [[[
interpolate (y0, objX0Y0Z0) (y1', objX0Y1Z0) (appAC obj x0 z0) res
| x0 <- pXs | objX0Y0Z0 <- objY0Z0 | objX0Y1Z0 <- objY1Z0
]| y0 <- pYs | y1' <- tail pYs | objY0Z0 <- objZ0 | objY1Z0 <- tail objZ0
]| z0 <- pZs | objZ0 <- objV
] `using` (parBuffer (max 1 $ fromInteger $ div nz 32) rdeepseq)
] `using` parBuffer (max 1 $ fromIntegral $ div ny 32) rdeepseq
midsX = [[[
interpolate (x0, objX0Y0Z0) (x1', objX1Y0Z0) (appBC obj y0 z0) res
| x0 <- pXs | x1' <- tail pXs | objX0Y0Z0 <- objY0Z0 | objX1Y0Z0 <- tail objY0Z0
]| y0 <- pYs | objY0Z0 <- objZ0
]| z0 <- pZs | objZ0 <- objV
] `using` (parBuffer (max 1 $ fromInteger $ div nz 32) rdeepseq)
] `using` parBuffer (max 1 $ fromIntegral $ div nx 32) rdeepseq
-- Calculate segments for each side
segsZ = [[[
map2 (inj3 z0) $ getSegs (x0,y0) (x1',y1') (obj **$ z0)
map (injZ z0) $ getSegs (x0,y0) (x1',y1') (obj **$ z0)
(objX0Y0Z0, objX1Y0Z0, objX0Y1Z0, objX1Y1Z0)
(midA0, midA1, midB0, midB1)
|x0<-pXs|x1'<-tail pXs|midB0<-mX'' |midB1<-mX'T |midA0<-mY'' |midA1<-tail mY''
@ -135,10 +134,10 @@ getMesh p1@(x1,y1,z1) p2 res obj =
|objY0Z0 <- objZ0 | objY1Z0 <- tail objZ0
]|z0<-pZs |mX' <-midsX| mY' <-midsY
|objZ0 <- objV
] `using` (parBuffer (max 1 $ fromInteger $ div nz 32) rdeepseq)
] `using` parBuffer (max 1 $ fromIntegral $ div nz 32) rdeepseq
segsY = [[[
map2 (inj2 y0) $ getSegs (x0,z0) (x1',z1') (obj *$* y0)
map (injY y0) $ getSegs (x0,z0) (x1',z1') (obj *$* y0)
(objX0Y0Z0,objX1Y0Z0,objX0Y0Z1,objX1Y0Z1)
(midA0, midA1, midB0, midB1)
|x0<-pXs|x1'<-tail pXs|midB0<-mB'' |midB1<-mBT' |midA0<-mA'' |midA1<-tail mA''
@ -147,10 +146,10 @@ getMesh p1@(x1,y1,z1) p2 res obj =
|objY0Z0 <- objZ0 | objY0Z1 <- objZ1
]|z0<-pZs|z1'<-tail pZs|mB' <-midsX|mBT <-tail midsX|mA' <-midsZ
|objZ0 <- objV | objZ1 <- tail objV
] `using` (parBuffer (max 1 $ fromInteger $ div nz 32) rdeepseq)
] `using` parBuffer (max 1 $ fromIntegral $ div ny 32) rdeepseq
segsX = [[[
map2 (inj1 x0) $ getSegs (y0,z0) (y1',z1') (obj $** x0)
map (injX x0) $ getSegs (y0,z0) (y1',z1') (obj $** x0)
(objX0Y0Z0,objX0Y1Z0,objX0Y0Z1,objX0Y1Z1)
(midA0, midA1, midB0, midB1)
|x0<-pXs| midB0<-mB'' |midB1<-mBT' |midA0<-mA'' |midA1<-mA'T
@ -159,7 +158,7 @@ getMesh p1@(x1,y1,z1) p2 res obj =
|objY0Z0 <-objZ0 |objY1Z0 <-tail objZ0 |objY0Z1 <-objZ1 |objY1Z1 <-tail objZ1
]|z0<-pZs|z1'<-tail pZs|mB' <-midsY|mBT <-tail midsY|mA' <-midsZ
|objZ0 <- objV | objZ1 <- tail objV
] `using` (parBuffer (max 1 $ fromInteger $ div nz 32) rdeepseq)
] `using` parBuffer (max 1 $ fromIntegral $ div nx 32) rdeepseq
-- (3) & (4) : get and tesselate loops
sqTris = [[[
@ -183,49 +182,38 @@ getMesh p1@(x1,y1,z1) p2 res obj =
]| segZ' <- segsZ | segZT <- tail segsZ
| segY' <- segsY
| segX' <- segsX
] `using` (parBuffer (max 1 $ fromInteger $ div nz 32) rdeepseq)
] `using` parBuffer (max 1 $ fromIntegral $ div nz 32) rdeepseq
in cleanupTris $ mergedSquareTris $ concat $ concat $ concat sqTris -- (5) merge squares, etc
-- Removes triangles that are empty, when converting their positions to Float resolution.
-- NOTE: this will need to be disabled for AMF, and other triangle formats that can handle Double.
cleanupTris :: TriangleMesh -> TriangleMesh
cleanupTris tris =
let
toFloat :: -> Float
toFloat = realToFrac
floatPoint :: (, , ) -> (Float, Float, Float)
floatPoint (a,b,c) = (toFloat a, toFloat b, toFloat c)
isDegenerateTriFloat :: Eq t => (t,t,t) -> Bool
isDegenerateTriFloat (a,b,c) = (a == b) || (b == c) || (a == c)
isDegenerateTri :: Triangle -> Bool
isDegenerateTri (a, b, c) = isDegenerateTriFloat (floatPoint a, floatPoint b, floatPoint c)
in filter (not . isDegenerateTri) tris
in
-- (5) merge squares, etc
mergedSquareTris . concat . concat $ concat sqTris
-- getContour gets a polyline describing the edge of a 2D object.
getContour :: 2 -> 2 -> -> Obj2 -> [Polyline]
getContour p1@(x1, y1) p2 res obj =
let
(dx,dy) = p2 ^-^ p1
-- the size of the region we're being asked to search.
d = p2 ^-^ p1
-- How many steps will we take on each axis?
nx :: Integral a => a
nx = ceiling $ dx / res
ny :: Integral a => a
ny = ceiling $ dy / res
nx ::
ny ::
(nx,ny) = ceiling `both` (d / (res,res))
rx = dx/fromInteger nx
ry = dy/fromInteger ny
-- How big are the steps?
(rx,ry) = d / (fromto `both` (nx,ny))
pYs = [ y1 + ry*n | n <- [0.. fromInteger ny] ]
pXs = [ x1 + rx*n | n <- [0.. fromInteger nx] ]
-- the points inside of the region.
pYs = [ y1 + ry*fromto p | p <- [0.. ny] ]
pXs = [ x1 + rx*fromto p | p <- [0.. nx] ]
par2DList :: forall t. NFData t => Integer -> Integer -> ((Integer -> ) -> Integer -> (Integer -> ) -> Integer -> t) -> [[t]]
par2DList :: forall t. NFData t => -> -> (( -> ) -> -> ( -> ) -> -> t) -> [[t]]
par2DList lenx leny f =
[[ f
(\n -> x1 + rx*fromIntegral (mx+n)) mx
(\n -> y1 + ry*fromIntegral (my+n)) my
(\n -> x1 + rx*fromto (mx+n)) mx
(\n -> y1 + ry*fromto (my+n)) my
| mx <- [0..lenx] ] | my <- [0..leny] ]
`using` (parBuffer (max 1 $ fromInteger $ div leny 32) rdeepseq)
`using` parBuffer (max 1 . fromIntegral $ div leny 32) rdeepseq
-- Evaluate obj to avoid waste in mids, segs, later.
@ -238,13 +226,13 @@ getContour p1@(x1, y1) p2 res obj =
interpolate (y0, objX0Y0) (y1', objX0Y1) (obj $* x0) res
| x0 <- pXs | objX0Y0 <- objY0 | objX0Y1 <- objY1
]| y0 <- pYs | y1' <- tail pYs | objY0 <- objV | objY1 <- tail objV
] `using` (parBuffer (max 1 $ fromInteger $ div ny 32) rdeepseq)
] `using` parBuffer (max 1 . fromIntegral $ div ny 32) rdeepseq
midsX = [[
interpolate (x0, objX0Y0) (x1', objX1Y0) (obj *$ y0) res
| x0 <- pXs | x1' <- tail pXs | objX0Y0 <- objY0 | objX1Y0 <- tail objY0
]| y0 <- pYs | objY0 <- objV
] `using` (parBuffer (max 1 $ fromInteger $ div ny 32) rdeepseq)
] `using` parBuffer (max 1 . fromIntegral $ div nx 32) rdeepseq
-- Calculate segments for each side
@ -256,21 +244,25 @@ getContour p1@(x1, y1) p2 res obj =
|objX0Y0<-objY0|objX1Y0<-tail objY0|objX0Y1<-objY1|objX1Y1<-tail objY1
]|y0<-pYs|y1'<-tail pYs|mX'' <-midsX|mX'T <-tail midsX|mY'' <-midsY
|objY0 <- objV | objY1 <- tail objV
] `using` (parBuffer (max 1 $ fromInteger $ div ny 32) rdeepseq)
in cleanLoopsFromSegs $ concat $ concat $ segs -- (5) merge squares, etc
] `using` parBuffer (max 1 . fromIntegral $ div ny 32) rdeepseq
in
cleanLoopsFromSegs . concat $ concat segs -- (5) merge squares, etc
-- utility functions
inj1 :: forall t t1 t2. t -> (t1, t2) -> (t, t1, t2)
inj1 a (b,c) = (a,b,c)
inj2 :: forall t t1 t2. t1 -> (t, t2) -> (t, t1, t2)
inj2 b (a,c) = (a,b,c)
inj3 :: forall t t1 t2. t2 -> (t, t1) -> (t, t1, t2)
inj3 c (a,b) = (a,b,c)
injX :: -> Polyline -> [3]
injX a (Polyline xs) = map (prepend a) xs
prepend :: -> 2 -> 3
prepend a (b,c) = (a,b,c)
injY :: -> Polyline -> [3]
injY a (Polyline xs) = map (insert a) xs
insert :: -> 2 -> 3
insert b (a,c) = (a,b,c)
injZ :: -> Polyline -> [3]
injZ a (Polyline xs) = map (postfix a) xs
postfix :: -> 2 -> 3
postfix c (a,b) = (a,b,c)
($**) :: forall t t1 t2 t3. ((t1, t2, t3) -> t) -> t1 -> (t2, t3) -> t
infixr 0 $**
@ -291,16 +283,12 @@ f *$* b = \(a,c) -> f (a,b,c)
f **$ c = \(a,b) -> f (a,b,c)
appAB :: forall t t1 t2 t3. ((t1, t2, t3) -> t) -> t1 -> t2 -> t3 -> t
appAB f a b = \c -> f (a,b,c)
appAB f a b c = f (a,b,c)
appBC :: forall t t1 t2 t3. ((t1, t2, t3) -> t) -> t2 -> t3 -> t1 -> t
appBC f b c = \a -> f (a,b,c)
appBC f b c a = f (a,b,c)
appAC :: forall t t1 t2 t3. ((t1, t2, t3) -> t) -> t1 -> t3 -> t2 -> t
appAC f a c = \b -> f (a,b,c)
appAC f a c b = f (a,b,c)
map2 :: forall a b. (a -> b) -> [[a]] -> [[b]]
map2 f = map (map f)
map2R :: forall a a1. (a1 -> a) -> [[a1]] -> [[a]]
map2R f = map (reverse . map f)
mapR :: forall a. [[a]] -> [[a]]
mapR = map reverse

View File

@ -1,25 +1,18 @@
-- Implicit CAD. Copyright (C) 2011, Christopher Olah (chris@colah.ca)
-- Released under the GNU AGPLV3+, see LICENSE
-- We want a type that can represent squares/quads and triangles.
module Graphics.Implicit.Export.Render.Definitions (TriSquare(Tris, Sq)) where
import Prelude()
import Graphics.Implicit.Definitions(, 2, 3, Triangle)
import Graphics.Implicit.Definitions(, 2, 3, TriangleMesh)
import Control.DeepSeq (NFData, rnf)
-- We want a format that can represent squares/quads and triangles.
-- So that we can merge squares and thereby reduces triangles.
-- Regarding Sq: Sq Basis@(b1,b2,b3) (Height on b3)
-- (b1 pos 1, b2 pos 1) (b1 pos 2, b2 pos 2)
data TriSquare =
Sq (3,3,3) 2 2
| Tris [Triangle]
-- For use with Parallel.Strategies later
| Tris TriangleMesh
instance NFData TriSquare where
rnf (Sq b z xS yS) = rnf (b,z,xS,yS)

View File

@ -8,8 +8,9 @@
module Graphics.Implicit.Export.Render.GetLoops (getLoops) where
-- Explicitly include what we want from Prelude.
import Prelude (Eq, head, last, tail, (==), Bool(False), filter, not, (.), null, error, (++))
import Prelude (Eq, head, last, tail, (==), Bool(False), (.), null, error, (++))
import Data.List (partition)
-- The goal of getLoops is to extract loops from a list of segments.
-- The input is a list of segments.
@ -51,7 +52,7 @@ getLoops' [] [] = []
getLoops' (x:xs) [] = getLoops' xs [x]
-- A loop is finished if its start and end are the same.
-- In this case, we return it and empty the building loop.
-- In this case, we return it and start searching for another loop.
getLoops' segs workingLoop | head (head workingLoop) == last (last workingLoop) =
workingLoop : getLoops' segs []
@ -60,16 +61,14 @@ getLoops' segs workingLoop | head (head workingLoop) == last (last workingLoop)
-- and stick one on if we find it.
-- Otherwise... something is really screwed up.
-- FIXME: connects should be used with a singleton.
getLoops' segs workingLoop =
let
presEnd :: forall c. [[c]] -> c
presEnd = last . last
connects (x:_) = x == presEnd workingLoop
connects [] = False -- Handle the empty case.
possibleConts = filter connects segs
nonConts = filter (not . connects) segs
-- divide our set into sequences that connect, and sequences that don't.
(possibleConts,nonConts) = partition connects segs
(next, unused) = if null possibleConts
then error "unclosed loop in paths given"
else (head possibleConts, tail possibleConts ++ nonConts)

View File

@ -4,9 +4,9 @@
module Graphics.Implicit.Export.Render.GetSegs (getSegs, getSegs') where
import Prelude(Eq, Bool(True, False), sqrt, (+), (*), (/=), map, (.), filter, ($), (<=))
import Prelude(Bool(True, False), sqrt, (+), (*), (/=), map, (.), filter, ($), (<=))
import Graphics.Implicit.Definitions (, 2, Obj2, Polyline)
import Graphics.Implicit.Definitions (, 2, Obj2, Polyline(Polyline))
import Graphics.Implicit.Export.Render.RefineSegs (refine)
import Graphics.Implicit.Export.Util (centroid)
@ -73,17 +73,15 @@ getSegs p1 p2 obj (x1y1, x2y1, x1y2, x2y2) (midx1V,midx2V,midy1V,midy2V) =
midy1 = (midy1V , y )
midy2 = (midy2V, y + dy)
notPointLine :: Eq a => [a] -> Bool
notPointLine (np1:np2:[]) = np1 /= np2
notPointLine [] = False
notPointLine [_] = False
notPointLine (_ : (_ : (_ : _))) = False
notPointLine :: Polyline -> Bool
notPointLine (Polyline [np1,np2]) = np1 /= np2
notPointLine _ = False
-- takes straight lines between mid points and subdivides them to
-- account for sharp corners, etc.
in map (refine res obj) . filter (notPointLine) $ case (x1y2 <= 0, x2y2 <= 0,
x1y1 <= 0, x2y1 <= 0) of
in map (refine res obj) . filter notPointLine $ case (x1y2 <= 0, x2y2 <= 0,
x1y1 <= 0, x2y1 <= 0) of
-- An important point here is orientation. If you imagine going along a
-- generated segment, the interior should be on the left-hand side.
@ -99,56 +97,56 @@ getSegs p1 p2 obj (x1y1, x2y1, x1y2, x2y2) (midx1V,midx2V,midy1V,midy2V) =
-- Horizontal Cases
(True, True,
False, False) -> [[midx1, midx2]]
False, False) -> [Polyline [midx1, midx2]]
(False, False,
True, True) -> [[midx2, midx1]]
True, True) -> [Polyline [midx2, midx1]]
-- Vertical Cases
(False, True,
False, True) -> [[midy2, midy1]]
False, True) -> [Polyline [midy2, midy1]]
(True, False,
True, False) -> [[midy1, midy2]]
True, False) -> [Polyline [midy1, midy2]]
-- Corner Cases
(True, False,
False, False) -> [[midx1, midy2]]
False, False) -> [Polyline [midx1, midy2]]
(False, True,
True, True) -> [[midy2, midx1]]
True, True) -> [Polyline [midy2, midx1]]
(True, True,
False, True) -> [[midx1, midy1]]
False, True) -> [Polyline [midx1, midy1]]
(False, False,
True, False) -> [[midy1, midx1]]
True, False) -> [Polyline [midy1, midx1]]
(True, True,
True, False) -> [[midy1, midx2]]
True, False) -> [Polyline [midy1, midx2]]
(False, False,
False, True) -> [[midx2, midy1]]
False, True) -> [Polyline [midx2, midy1]]
(True, False,
True, True) -> [[midx2, midy2]]
True, True) -> [Polyline [midx2, midy2]]
(False, True,
False, False) -> [[midy2, midx2]]
False, False) -> [Polyline [midy2, midx2]]
-- Dual Corner Cases
(True, False,
False, True) -> if c <= 0
then [[midx1, midy1], [midx2, midy2]]
else [[midx1, midy2], [midx2, midy1]]
then [Polyline [midx1, midy1], Polyline [midx2, midy2]]
else [Polyline [midx1, midy2], Polyline [midx2, midy1]]
(False, True,
True, False) -> if c <= 0
then [[midy2, midx1], [midy1, midx2]]
else [[midy1, midx1], [midy2, midx2]]
then [Polyline [midy2, midx1], Polyline [midy1, midx2]]
else [Polyline [midy1, midx1], Polyline [midy2, midx2]]
-- A convenience function, we don't actually care too much about

View File

@ -7,9 +7,9 @@
module Graphics.Implicit.Export.Render.HandlePolylines (cleanLoopsFromSegs, reducePolyline) where
import Prelude(Bool(False), Maybe(Just, Nothing), map, (.), filter, (==), last, reverse, ($), (++), tail, (-), (/), abs, (<=), (||), (&&), (*), (>), not, null)
import Prelude(Bool(True, False), Maybe(Just, Nothing), map, (.), filter, (==), last, reverse, ($), (++), (-), (/), abs, (<=), (||), (&&), (*), (>), otherwise)
import Graphics.Implicit.Definitions (min, Polyline, )
import Graphics.Implicit.Definitions (min, Polyline(Polyline))
cleanLoopsFromSegs :: [Polyline] -> [Polyline]
cleanLoopsFromSegs =
@ -17,34 +17,45 @@ cleanLoopsFromSegs =
. joinSegs
. filter polylineNotNull
-- | Join polylines that connect.
joinSegs :: [Polyline] -> [Polyline]
joinSegs [] = []
joinSegs (present:remaining) =
joinSegs (Polyline present:remaining) =
let
findNext ((p3:ps):segs) = if p3 == last present then (Just (p3:ps), segs) else
if last ps == last present then (Just (reverse $ p3:ps), segs) else
case findNext segs of (res1,res2) -> (res1,(p3:ps):res2)
findNext :: [Polyline] -> (Maybe Polyline, [Polyline])
findNext (Polyline (p3:ps):segs)
| p3 == last present = (Just (Polyline (p3:ps)), segs)
| last ps == last present = (Just (Polyline $ reverse $ p3:ps), segs)
| otherwise = case findNext segs of (res1,res2) -> (res1,(Polyline (p3:ps)):res2)
findNext [] = (Nothing, [])
findNext (([]):_) = (Nothing, [])
findNext (Polyline []:_) = (Nothing, [])
in
case findNext remaining of
(Nothing, _) -> present:(joinSegs remaining)
(Just match, others) -> joinSegs $ (present ++ tail match): others
(Nothing, _) -> Polyline present: joinSegs remaining
(Just (Polyline match), others) -> joinSegs $ (Polyline (present ++ match)) : others
reducePolyline :: [(, )] -> [(, )]
reducePolyline ((x1,y1):(x2,y2):(x3,y3):others) =
if (x1,y1) == (x2,y2) then reducePolyline ((x2,y2):(x3,y3):others) else
if abs ( (y2-y1)/(x2-x1) - (y3-y1)/(x3-x1) ) <= min
|| ( (x2-x1) == 0 && (x3-x1) == 0 && (y2-y1)*(y3-y1) > 0)
then reducePolyline ((x1,y1):(x3,y3):others)
else (x1,y1) : reducePolyline ((x2,y2):(x3,y3):others)
reducePolyline ((x1,y1):(x2,y2):others) =
if (x1,y1) == (x2,y2) then reducePolyline ((x2,y2):others) else (x1,y1):(x2,y2):others
-- | Simplify and sort a polyline.
reducePolyline :: Polyline -> Polyline
reducePolyline (Polyline ((x1,y1):(x2,y2):(x3,y3):others))
-- Remove duplicate points.
| (x1,y1) == (x2,y2) = reducePolyline (Polyline ((x2,y2):(x3,y3):others))
| abs ( (y2-y1)/(x2-x1) - (y3-y1)/(x3-x1) ) <= min
|| ( (x2-x1) == 0 && (x3-x1) == 0 && (y2-y1)*(y3-y1) > 0) =
reducePolyline (Polyline ((x1,y1):(x3,y3):others))
| otherwise = Polyline ((x1,y1) : (points $ reducePolyline (Polyline ((x2,y2):(x3,y3):others))))
where
points (Polyline pts) = pts
-- | remove duplicate points
reducePolyline (Polyline ((x1,y1):(x2,y2):others)) =
if (x1,y1) == (x2,y2) then reducePolyline (Polyline ((x2,y2):others)) else Polyline ((x1,y1):(x2,y2):others)
-- | Return the last result.
reducePolyline l = l
polylineNotNull :: [a] -> Bool
polylineNotNull (_:l) = not (null l)
polylineNotNull [] = False
-- ensure that polylines are not empty.
polylineNotNull :: Polyline -> Bool
polylineNotNull (Polyline (_:_:_)) = True
polylineNotNull (Polyline [_]) = True
polylineNotNull (Polyline []) = False
{-cleanLoopsFromSegs =
connectPolys

View File

@ -4,9 +4,9 @@
module Graphics.Implicit.Export.Render.HandleSquares (mergedSquareTris) where
import Prelude(concatMap, (++))
import Prelude(concatMap, (++), ($))
import Graphics.Implicit.Definitions (Triangle)
import Graphics.Implicit.Definitions (TriangleMesh(TriangleMesh), Triangle(Triangle))
import Graphics.Implicit.Export.Render.Definitions (TriSquare(Tris, Sq))
import Data.VectorSpace ((^*), (*^), (^+^))
@ -57,16 +57,21 @@ import Data.VectorSpace ((^*), (*^), (^+^))
-}
mergedSquareTris :: [TriSquare] -> [Triangle]
mergedSquareTris :: [TriSquare] -> TriangleMesh
mergedSquareTris sqTris =
let
-- We don't need to do any work on triangles. They'll just be part of
-- the list of triangles we give back. So, the triangles coming from
-- triangles...
triTriangles = [tri | Tris tris <- sqTris, tri <- tris ]
triTriangles :: [Triangle]
triTriangles = [tri | Tris tris <- sqTris, tri <- unmesh tris ]
--concat $ map (\(Tris a) -> a) $ filter isTris sqTris
-- We actually want to work on the quads, so we find those
squaresFromTris = [ (Sq x y z q) | Sq x y z q <- sqTris ]
squaresFromTris :: [TriSquare]
squaresFromTris = [ Sq x y z q | Sq x y z q <- sqTris ]
unmesh (TriangleMesh m) = m
{-
-- Collect ones that are on the same plane.
planeAligned = groupWith (\(Sq basis z _ _) -> (basis,z)) squares
@ -85,7 +90,7 @@ mergedSquareTris sqTris =
-- merge them to triangles, and combine with the original triangles.
-- Disable square merging temporarily.
--triTriangles ++ concat (map squareToTri finishedSquares)
triTriangles ++ concatMap squareToTri squaresFromTris
TriangleMesh $ triTriangles ++ concatMap squareToTri squaresFromTris
-- And now for a bunch of helper functions that do the heavy lifting...
@ -125,7 +130,7 @@ joinYaligned quads@((Sq b z _ yS):_) =
joinYaligned [] = []
-}
-- Reconstruct a triangle
-- Deconstruct a square into two triangles.
squareToTri :: TriSquare -> [Triangle]
squareToTri (Sq (b1,b2,b3) z (x1,x2) (y1,y2)) =
let
@ -137,8 +142,9 @@ squareToTri (Sq (b1,b2,b3) z (x1,x2) (y1,y2)) =
c = zV ^+^ x1V ^+^ y2V
d = zV ^+^ x2V ^+^ y2V
in
[(a,b,c),(c,b,d)]
squareToTri(Tris t) = t
[Triangle (a,b,c), Triangle (c,b,d)]
squareToTri (Tris t) = unmesh t
where
unmesh (TriangleMesh a) = a

View File

@ -7,9 +7,9 @@
module Graphics.Implicit.Export.Render.Interpolate (interpolate) where
import Prelude(Integer, (*), (>), (<), (/=), (+), (-), (/), (==), (&&), abs)
import Prelude((*), (>), (<), (/=), (+), (-), (/), (==), (&&), abs)
import Graphics.Implicit.Definitions (, 2)
import Graphics.Implicit.Definitions (, , 2)
-- Consider a function f(x):
@ -43,6 +43,8 @@ import Graphics.Implicit.Definitions (, 2)
-- If it doesn't cross zero, we don't actually care what answer we give,
-- just that it's cheap.
-- FIXME: accept resolution on multiple axises.
interpolate :: 2 -> 2 -> ( -> ) -> ->
interpolate (a,aval) (_,bval) _ _ | aval*bval > 0 = a
@ -89,38 +91,38 @@ interpolate _ (b, 0) _ _ = b
-- The best case is that it crosses between a and a'
if aval*a'val < 0
then
interpolate_bin 0 (a,aval) (a',a'val) f
interpolateBin 0 (a,aval) (a',a'val) f
-- Or between b' and b
else if bval*b'val < 0
then interpolate_bin 0 (b',b'val) (b,bval) f
then interpolateBin 0 (b',b'val) (b,bval) f
-- But in the worst case, we get to shrink to (a',b') :)
else interpolate_bin 0 (a',a'val) (b',b'val) f
else interpolateBin 0 (a',a'val) (b',b'val) f
-- Otherwise, we use our friend, linear interpolation!
else
-- again...
-- The best case is that it crosses between a and a'
if aval*a'val < 0
then
interpolate_lin 0 (a,aval) (a',a'val) f
interpolateLin 0 (a,aval) (a',a'val) f
-- Or between b' and b
else if bval*b'val < 0
then interpolate_lin 0 (b',b'val) (b,bval) f
then interpolateLin 0 (b',b'val) (b,bval) f
-- But in the worst case, we get to shrink to (a',b') :)
else interpolate_lin 0 (a',a'val) (b',b'val) f
else interpolateLin 0 (a',a'val) (b',b'val) f
-}
interpolate (a,aval) (b,bval) f _ =
-- Make sure aval > bval, then pass to interpolate_lin
-- Make sure aval > bval, then pass to interpolateLin
if aval > bval
then interpolate_lin 0 (a,aval) (b,bval) f
else interpolate_lin 0 (b,bval) (a,aval) f
then interpolateLin 0 (a,aval) (b,bval) f
else interpolateLin 0 (b,bval) (a,aval) f
-- Yay, linear interpolation!
-- Try the answer linear interpolation gives us...
-- (n is to cut us off if recursion goes too deep)
interpolate_lin :: Integer -> 2 -> 2 -> ( -> ) ->
interpolate_lin n (a, aval) (b, bval) obj | aval /= bval=
interpolateLin :: -> 2 -> 2 -> ( -> ) ->
interpolateLin n (a, aval) (b, bval) obj | aval /= bval=
let
-- Interpolate and evaluate
mid ::
@ -144,32 +146,32 @@ interpolate_lin n (a, aval) (b, bval) obj | aval /= bval=
-- to zero than the previous one.
in if improveRatio < 0.3 && n < 4
-- And we continue on.
then interpolate_lin (n+1) (a', a'val) (b', b'val) obj
then interpolateLin (n+1) (a', a'val) (b', b'val) obj
-- But if not, we switch to binary interpolate, which is
-- immune to this problem
else interpolate_bin (n+1) (a', a'val) (b', b'val) obj
else interpolateBin (n+1) (a', a'val) (b', b'val) obj
-- And a fallback:
interpolate_lin _ (a, _) _ _ = a
interpolateLin _ (a, _) _ _ = a
-- Now for binary searching!
interpolate_bin :: Integer -> 2 -> 2 -> ( -> ) ->
interpolateBin :: -> 2 -> 2 -> ( -> ) ->
-- The termination case:
interpolate_bin 5 (a,aval) (b,bval) _ =
interpolateBin 5 (a,aval) (b,bval) _ =
if abs aval < abs bval
then a
else b
-- Otherwise, have fun with mid!
interpolate_bin n (a,aval) (b,bval) f =
interpolateBin n (a,aval) (b,bval) f =
let
mid ::
mid = (a+b)/2
midval = f mid
in if midval > 0
then interpolate_bin (n+1) (mid,midval) (b,bval) f
else interpolate_bin (n+1) (a,aval) (mid,midval) f
then interpolateBin (n+1) (mid,midval) (b,bval) f
else interpolateBin (n+1) (a,aval) (mid,midval) f

View File

@ -2,11 +2,12 @@
-- Copyright (C) 2016, Julia Longtin (julial@turinglace.com)
-- Released under the GNU AGPLV3+, see LICENSE
-- export one function, which refines polylines.
module Graphics.Implicit.Export.Render.RefineSegs (refine) where
import Prelude(Int, (<), (/), (++), (*), ($), (&&), (-), (+), (.), (>), abs, tail, sqrt, (<=))
import Prelude((<), (/), (++), (*), ($), (&&), (-), (+), (.), (>), abs, sqrt, (<=))
import Graphics.Implicit.Definitions (, 2, min, Obj2, ())
import Graphics.Implicit.Definitions (, 2, Polyline(Polyline), min, , Obj2, ())
import Graphics.Implicit.Export.Util (centroid)
import Data.VectorSpace (normalized, magnitude, (^-^), (^*), (^+^))
@ -16,64 +17,73 @@ import Data.VectorSpace (normalized, magnitude, (^-^), (^*), (^+^))
-- We break this into two steps: detail and then simplify.
refine :: -> Obj2 -> [2] -> [2]
refine :: -> Obj2 -> Polyline -> Polyline
refine res obj = simplify res . detail' res obj
-- we wrap detail to make it ignore very small segments, and to pass in
-- an initial value for a pointer counter argument. This is detail'
-- FIXME: magic number.
detail' :: -> (2 -> ) -> [2] -> [2]
detail' res obj [p1@(x1,y1), p2@(x2,y2)] | (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) > res*res/200 =
detail 0 res obj [p1,p2]
detail' :: -> (2 -> ) -> Polyline -> Polyline
detail' res obj (Polyline [p1@(x1,y1), p2@(x2,y2)])
| (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) > res*res/200 = detail 0 res obj $ Polyline [p1,p2]
detail' _ _ a = a
-- detail adds new points to a polyline to add more detail.
detail :: Int -> -> (2 -> ) -> [2] -> [2]
detail n res obj [p1, p2] | n < 2 =
-- FIXME: all of the magic numbers.
-- | detail adds new points to a polyline to add more detail.
detail :: -> -> (2 -> ) -> Polyline -> Polyline
detail n res obj (Polyline [p1, p2]) | n < 2 =
let
mid = centroid [p1,p2]
midval = obj mid
in if abs midval < res / 40
then [p1, p2]
else let
normal = (\(a,b) -> (b, -a)) $ normalized (p2 ^-^ p1)
derivN = -(obj (mid ^-^ (normal ^* (midval/2))) - midval) * (2/midval)
in if abs derivN > 0.5 && abs derivN < 2 && abs (midval/derivN) < 3*res
then let
mid' = mid ^-^ (normal ^* (midval / derivN))
in detail (n+1) res obj [p1, mid']
++ tail (detail (n+1) res obj [mid', p2] )
else let
derivX = (obj (mid ^+^ (res/100, 0)) - midval)*100/res
derivY = (obj (mid ^+^ (0, res/100)) - midval)*100/res
derivNormSq = derivX*derivX + derivY*derivY
in if abs derivNormSq > 0.09 && abs derivNormSq < 4 && abs (midval/sqrt derivNormSq) < 3*res
then let
(dX, dY) = (- derivX*midval/derivNormSq, - derivY*midval/derivNormSq)
mid' = mid ^+^ (dX, dY)
midval' = obj mid'
posRatio = midval/(midval - midval')
mid'' = mid ^+^ (dX*posRatio, dY*posRatio)
in
detail (n+1) res obj [p1, mid''] ++ tail (detail (n+1) res obj [mid'', p2] )
else [p1, p2]
then Polyline [p1, p2]
else
let
normal = (\(a,b) -> (b, -a)) $ normalized (p2 ^-^ p1)
derivN = -(obj (mid ^-^ (normal ^* (midval/2))) - midval) * (2/midval)
in
if abs derivN > 0.5 && abs derivN < 2 && abs (midval/derivN) < 3*res
then
let
mid' = mid ^-^ (normal ^* (midval / derivN))
in
addPolylines (detail (n+1) res obj (Polyline [p1, mid'])) (detail (n+1) res obj ( Polyline [mid', p2] ))
else
let
derivX = (obj (mid ^+^ (res/100, 0)) - midval)*100/res
derivY = (obj (mid ^+^ (0, res/100)) - midval)*100/res
derivNormSq = derivX*derivX + derivY*derivY
in
if abs derivNormSq > 0.09 && abs derivNormSq < 4 && abs (midval/sqrt derivNormSq) < 3*res
then
let
(dX, dY) = (- derivX*midval/derivNormSq, - derivY*midval/derivNormSq)
mid' = mid ^+^ (dX, dY)
midval' = obj mid'
posRatio = midval/(midval - midval')
mid'' = mid ^+^ (dX*posRatio, dY*posRatio)
in
addPolylines (detail (n+1) res obj (Polyline [p1, mid''])) (detail (n+1) res obj ( Polyline [mid'', p2] ))
else Polyline [p1, p2]
detail _ _ _ x = x
simplify :: -> [2] -> [2]
simplify :: -> Polyline -> Polyline
simplify _ = {-simplify3 . simplify2 res . -} simplify1
simplify1 :: [2] -> [2]
simplify1 (a:b:c:xs) =
simplify1 :: Polyline -> Polyline
simplify1 (Polyline (a:b:c:xs)) =
if abs ( ((b ^-^ a) (c ^-^ a)) - magnitude (b ^-^ a) * magnitude (c ^-^ a) ) <= min
then simplify1 (a:c:xs)
else a : simplify1 (b:c:xs)
then simplify1 (Polyline (a:c:xs))
else addPolylines (Polyline [a]) (simplify1 (Polyline (b:c:xs)))
simplify1 a = a
addPolylines :: Polyline -> Polyline -> Polyline
addPolylines (Polyline as) (Polyline bs) = Polyline (as ++ bs)
{-
simplify2 :: -> [2] -> [2]
simplify2 :: -> Polyline -> Polyline
simplify2 res [a,b,c,d] =
if norm (b - c) < res/10
then [a, ((b + c) / (2::)), d]

View File

@ -4,18 +4,25 @@
module Graphics.Implicit.Export.Render.TesselateLoops (tesselateLoop) where
import Prelude(Int, return, ($), length, (==), zip, init, tail, reverse, (<), (/), null, foldl1, (++), head, (*), abs, (>), (&&), (+), concatMap)
import Graphics.Implicit.Definitions (, Obj3, 3, Triangle, ())
import Prelude(return, ($), length, (==), zip, init, tail, reverse, (<), (/), null, foldl1, (++), head, (*), abs, (>), (&&), (+), concatMap)
import Graphics.Implicit.Definitions (, , Obj3, 3, TriangleMesh(TriangleMesh), (), Triangle(Triangle))
import Graphics.Implicit.Export.Render.Definitions (TriSquare(Tris, Sq))
import Graphics.Implicit.Export.Util (centroid)
import Data.VectorSpace (normalized, (^-^), (^+^), magnitude, (^/), (^*))
import Data.List (genericLength)
import Data.Cross (cross3)
tesselateLoop :: -> Obj3 -> [[3]] -> [TriSquare]
tesselateLoop _ _ [] = []
tesselateLoop _ _ [[a,b],[_,c],[_,_]] = return $ Tris [(a,b,c)]
tesselateLoop _ _ [[a,b],[_,c],[_,_]] = [Tris $ TriangleMesh [Triangle (a,b,c)]]
{-
@ -27,12 +34,12 @@ tesselateLoop _ _ [[a,b],[_,c],[_,_]] = return $ Tris [(a,b,c)]
-}
tesselateLoop res obj [[_,_], as@(_:_:_:_),[_,_], bs@(_:_:_:_)] | length as == length bs =
concatMap (tesselateLoop res obj) $
concatMap (tesselateLoop res obj)
[[[a1,b1],[b1,b2],[b2,a2],[a2,a1]] | ((a1,b1),(a2,b2)) <- zip (init pairs) (tail pairs)]
where pairs = zip (reverse as) bs
tesselateLoop res obj [as@(_:_:_:_),[_,_], bs@(_:_:_:_), [_,_] ] | length as == length bs =
concatMap (tesselateLoop res obj) $
concatMap (tesselateLoop res obj)
[[[a1,b1],[b1,b2],[b2,a2],[a2,a1]] | ((a1,b1),(a2,b2)) <- zip (init pairs) (tail pairs)]
where pairs = zip (reverse as) bs
@ -58,11 +65,12 @@ tesselateLoop _ _ [[a,_],[b,_],[c,_],[d,_]] | centroid [a,c] == centroid [b,d] =
-}
tesselateLoop res obj [[a,_],[b,_],[c,_],[d,_]] | obj (centroid [a,c]) < res/30 =
return $ Tris $ [(a,b,c),(a,c,d)]
return $ Tris $ TriangleMesh [Triangle (a,b,c), Triangle (a,c,d)]
-- Fallback case: make fans
tesselateLoop res obj pathSides = return $ Tris $
-- FIXME: magic numbers.
tesselateLoop res obj pathSides = return $ Tris $ TriangleMesh $
let
path' = concatMap init pathSides
(early_tris,path) = shrinkLoop 0 path' res obj
@ -71,7 +79,7 @@ tesselateLoop res obj pathSides = return $ Tris $
else let
mid@(_,_,_) = centroid path
midval = obj mid
preNormal = foldl1 (^+^) $
preNormal = foldl1 (^+^)
[ a `cross3` b | (a,b) <- zip path (tail path ++ [head path]) ]
preNormalNorm = magnitude preNormal
normal = preNormal ^/ preNormalNorm
@ -79,24 +87,24 @@ tesselateLoop res obj pathSides = return $ Tris $
mid' = mid ^-^ normal ^* (midval/deriv)
in if abs midval > res/50 && preNormalNorm > 0.5 && abs deriv > 0.5
&& abs (midval/deriv) < 2*res && 3*abs (obj mid') < abs midval
then early_tris ++ [(a,b,mid') | (a,b) <- zip path (tail path ++ [head path]) ]
else early_tris ++ [(a,b,mid) | (a,b) <- zip path (tail path ++ [head path]) ]
then early_tris ++ [Triangle (a,b,mid') | (a,b) <- zip path (tail path ++ [head path]) ]
else early_tris ++ [Triangle (a,b,mid) | (a,b) <- zip path (tail path ++ [head path]) ]
shrinkLoop :: Int -> [3] -> -> Obj3 -> ([Triangle], [3])
shrinkLoop :: -> [3] -> -> Obj3 -> ([Triangle], [3])
shrinkLoop _ path@[a,b,c] res obj =
if abs (obj $ centroid [a,b,c]) < res/50
then
( [(a,b,c)], [])
( [Triangle (a,b,c)], [])
else
([], path)
shrinkLoop n path@(a:b:c:xs) res obj | n < length path =
shrinkLoop n path@(a:b:c:xs) res obj | n < genericLength path =
if abs (obj (centroid [a,c])) < res/50
then
let (tris,remainder) = shrinkLoop 0 (a:c:xs) res obj
in ((a,b,c):tris, remainder)
in ((Triangle (a,b,c)):tris, remainder)
else
shrinkLoop (n+1) (b:c:xs ++ [a]) res obj

View File

@ -2,7 +2,7 @@
-- Copyright (C) 2016, Julia Longtin (julial@turinglace.com)
-- Released under the GNU AGPLV3+, see LICENSE
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances, FlexibleContexts, TypeSynonymInstances, UndecidableInstances #-}
{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FlexibleContexts, TypeSynonymInstances, UndecidableInstances #-}
module Graphics.Implicit.Export.Symbolic.CoerceSymbolic2 (coerceSymbolic2) where

View File

@ -2,7 +2,7 @@
-- Copyright (C) 2016, Julia Longtin (julial@turinglace.com)
-- Released under the GNU AGPLV3+, see LICENSE
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances, FlexibleContexts, TypeSynonymInstances, UndecidableInstances #-}
{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FlexibleContexts, TypeSynonymInstances, UndecidableInstances #-}
-- We just want to export the instance...
module Graphics.Implicit.Export.Symbolic.CoerceSymbolic3 (coerceSymbolic3) where

View File

@ -16,4 +16,4 @@ rebound2 (obj, (a,b)) =
d :: 2
d = (b ^-^ a) ^/ 10
in
(obj, ((a ^-^ d), (b ^+^ d)))
(obj, (a ^-^ d, b ^+^ d))

View File

@ -10,11 +10,14 @@ import Graphics.Implicit.Definitions(BoxedObj3, 3)
import Data.VectorSpace((^-^), (^+^), (^/))
-- | Slightly stretch the bounding box of an object, in order to
-- ensure that during mesh generation, there are no problems because
-- values are right at the edge.
rebound3 :: BoxedObj3 -> BoxedObj3
rebound3 (obj, (a,b)) =
let
d :: 3
d = (b ^-^ a) ^/ 10
in
(obj, ((a ^-^ d), (b ^+^ d)))
(obj, (a ^-^ d, b ^+^ d))

View File

@ -8,27 +8,27 @@
-- output SCAD code, AKA an implicitcad to openscad converter.
module Graphics.Implicit.Export.SymbolicFormats (scad2, scad3) where
import Prelude(Maybe(Just, Nothing), Either(Left), ($), (.), (*), map, ($!), (-), (/), pi, error, (+), init, (==))
import Prelude(Maybe(Just, Nothing), Either(Left), ($), (.), (*), map, ($!), (-), (/), pi, error, (+), (==), take, floor)
import Graphics.Implicit.Definitions(, SymbolicObj2(RectR, Circle, PolygonR, Complement2, UnionR2, DifferenceR2, IntersectR2, Translate2, Scale2, Rotate2, Outset2, Shell2, EmbedBoxedObj2), SymbolicObj3(Rect3R, Sphere, Cylinder, Complement3, UnionR3, IntersectR3, DifferenceR3, Translate3, Scale3, Rotate3, Rotate3V, Outset3, Shell3, ExtrudeR, ExtrudeRotateR, ExtrudeRM, EmbedBoxedObj3, RotateExtrude, ExtrudeOnEdgeOf))
import Graphics.Implicit.Export.TextBuilderUtils(Text, Builder, toLazyText, (<>), mconcat, fromLazyText, bf)
import Control.Monad.Reader (Reader, runReader, return, fmap, sequence, ask)
import Data.List (intersperse)
import Data.List (intersperse, (++))
import Data.Function (fix)
scad2 :: -> SymbolicObj2 -> Text
scad2 :: -> SymbolicObj2 -> Text
scad2 res obj = toLazyText $ runReader (buildS2 obj) res
scad3 :: -> SymbolicObj3 -> Text
scad3 :: -> SymbolicObj3 -> Text
scad3 res obj = toLazyText $ runReader (buildS3 obj) res
-- used by rotate2 and rotate3
rad2deg :: ->
rad2deg r = r * (180/pi)
-- Format an openscad call given that all the modified objects are in the Reader monad...
-- | Format an openscad call given that all the modified objects are in the Reader monad...
callToken :: (Text, Text) -> Builder -> [Builder] -> [Reader a Builder] -> Reader a Builder
callToken cs name args [] = return $ name <> buildArgs cs args <> ";"
callToken cs name args [obj] = fmap ((name <> buildArgs cs args) <>) obj
@ -38,7 +38,7 @@ callToken cs name args objs = do
buildArgs :: (Text, Text) -> [Builder] -> Builder
buildArgs _ [] = "()"
buildArgs (c1, c2) args = "(" <> (fromLazyText c1) <> mconcat (intersperse "," args) <> (fromLazyText c2) <> ")"
buildArgs (c1, c2) args = "(" <> fromLazyText c1 <> mconcat (intersperse "," args) <> fromLazyText c2 <> ")"
call :: Builder -> [Builder] -> [Reader a Builder] -> Reader a Builder
call = callToken ("[", "]")
@ -46,7 +46,7 @@ call = callToken ("[", "]")
callNaked :: Builder -> [Builder] -> [Reader a Builder] -> Reader a Builder
callNaked = callToken ("", "")
-- First, the 3D objects.
-- | First, the 3D objects.
buildS3 :: SymbolicObj3 -> Reader Builder
buildS3 (Rect3R r (x1,y1,z1) (x2,y2,z2)) | r == 0 = call "translate" [bf x1, bf y1, bf z1] [
@ -55,7 +55,7 @@ buildS3 (Rect3R r (x1,y1,z1) (x2,y2,z2)) | r == 0 = call "translate" [bf x1, bf
buildS3 (Sphere r) = callNaked "sphere" ["r = " <> bf r] []
buildS3 (Cylinder h r1 r2) = call "cylinder" [
buildS3 (Cylinder h r1 r2) = callNaked "cylinder" [
"r1 = " <> bf r1
,"r2 = " <> bf r2
, bf h
@ -75,8 +75,7 @@ buildS3 (Scale3 (x,y,z) obj) = call "scale" [bf x, bf y, bf z] [buildS3 obj]
buildS3 (Rotate3 (x,y,z) obj) = call "rotate" [bf (rad2deg x), bf (rad2deg y), bf (rad2deg z)] [buildS3 obj]
-- FIXME: where is Rotate3V?
buildS3 (Rotate3V _ _ _) = error "Rotate3V not implemented."
buildS3 Rotate3V{} = error "Rotate3V not implemented."
buildS3 (Outset3 r obj) | r == 0 = call "outset" [] [buildS3 obj]
@ -94,23 +93,23 @@ buildS3 (ExtrudeRM r (Just twist) Nothing Nothing obj (Left height)) | r == 0 =
call "rotate" ["0","0", bf $ twist h] [
callNaked "linear_extrude" ["height = " <> bf res, "twist = " <> bf (twist (h+res) - twist h)][
buildS2 obj
]
] | h <- init [0, res .. height]
]
] | h <- take (floor (res / height)) $ fix (\f x -> [x] ++ f (x+res)) (0)
]
-- FIXME: where are RotateExtrude, ExtrudeOnEdgeOf?
buildS3(Rect3R _ _ _) = error "cannot provide roundness when exporting openscad; unsupported in target format."
buildS3 Rect3R{} = error "cannot provide roundness when exporting openscad; unsupported in target format."
buildS3(UnionR3 _ _) = error "cannot provide roundness when exporting openscad; unsupported in target format."
buildS3(IntersectR3 _ _) = error "cannot provide roundness when exporting openscad; unsupported in target format."
buildS3(DifferenceR3 _ _) = error "cannot provide roundness when exporting openscad; unsupported in target format."
buildS3(Outset3 _ _) = error "cannot provide roundness when exporting openscad; unsupported in target format."
buildS3(Shell3 _ _) = error "cannot provide roundness when exporting openscad; unsupported in target format."
buildS3(ExtrudeR _ _ _) = error "cannot provide roundness when exporting openscad; unsupported in target format."
buildS3(ExtrudeRotateR _ _ _ _) = error "cannot provide roundness when exporting openscad; unsupported in target format."
buildS3(ExtrudeRM _ _ _ _ _ _) = error "cannot provide roundness when exporting openscad; unsupported in target format."
buildS3 ExtrudeR{} = error "cannot provide roundness when exporting openscad; unsupported in target format."
buildS3 ExtrudeRotateR {} = error "cannot provide roundness when exporting openscad; unsupported in target format."
buildS3 ExtrudeRM{} = error "cannot provide roundness when exporting openscad; unsupported in target format."
buildS3(EmbedBoxedObj3 _) = error "cannot provide roundness when exporting openscad; unsupported in target format."
buildS3(RotateExtrude _ _ _ _ _) = error "cannot provide roundness when exporting openscad; unsupported in target format."
buildS3 RotateExtrude{} = error "cannot provide roundness when exporting openscad; unsupported in target format."
buildS3(ExtrudeOnEdgeOf _ _) = error "cannot provide roundness when exporting openscad; unsupported in target format."
-- Now the 2D objects/transforms.
@ -126,7 +125,7 @@ buildS2 (Circle r) = call "circle" [bf r] []
buildS2 (PolygonR r points) | r == 0 = call "polygon" [buildVector [x,y] | (x,y) <- points] []
where buildVector comps = "[" <> mconcat (intersperse "," $ map bf comps) <> "]"
buildS2 (Complement2 obj) = call "complement" [] $ [buildS2 obj]
buildS2 (Complement2 obj) = call "complement" [] [buildS2 obj]
buildS2 (UnionR2 r objs) | r == 0 = call "union" [] $ map buildS2 objs
@ -134,18 +133,18 @@ buildS2 (DifferenceR2 r objs) | r == 0 = call "difference" [] $ map buildS2 objs
buildS2 (IntersectR2 r objs) | r == 0 = call "intersection" [] $ map buildS2 objs
buildS2 (Translate2 (x,y) obj) = call "translate" [bf x, bf y] $ [buildS2 obj]
buildS2 (Translate2 (x,y) obj) = call "translate" [bf x, bf y] [buildS2 obj]
buildS2 (Scale2 (x,y) obj) = call "scale" [bf x, bf y] $ [buildS2 obj]
buildS2 (Scale2 (x,y) obj) = call "scale" [bf x, bf y] [buildS2 obj]
buildS2 (Rotate2 (r) obj) = call "rotate" [bf (rad2deg r)] $ [buildS2 obj]
buildS2 (Rotate2 r obj) = call "rotate" [bf (rad2deg r)] [buildS2 obj]
buildS2 (Outset2 r obj) | r == 0 = call "outset" [] $ [buildS2 obj]
buildS2 (Outset2 r obj) | r == 0 = call "outset" [] [buildS2 obj]
buildS2 (Shell2 r obj) | r == 0 = call "shell" [] $ [buildS2 obj]
buildS2 (Shell2 r obj) | r == 0 = call "shell" [] [buildS2 obj]
-- Generate errors for rounding requests. OpenSCAD does not support rounding.
buildS2 (RectR _ _ _) = error "cannot provide roundness when exporting openscad; unsupported in target format."
buildS2 RectR{} = error "cannot provide roundness when exporting openscad; unsupported in target format."
buildS2 (PolygonR _ _) = error "cannot provide roundness when exporting openscad; unsupported in target format."
buildS2 (UnionR2 _ _) = error "cannot provide roundness when exporting openscad; unsupported in target format."
buildS2 (DifferenceR2 _ _) = error "cannot provide roundness when exporting openscad; unsupported in target format."

View File

@ -3,17 +3,17 @@
-- Released under the GNU AGPLV3+, see LICENSE
-- FIXME: why is all of this needed?
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances, FlexibleContexts, TypeSynonymInstances, UndecidableInstances #-}
{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FlexibleContexts, TypeSynonymInstances, UndecidableInstances #-}
-- This file symbolicaly renders contours and contour fillings.
-- If it can't, it passes the puck to a marching-squares-like
-- algorithm...
module Graphics.Implicit.Export.SymbolicObj2 where
module Graphics.Implicit.Export.SymbolicObj2 (symbolicGetOrientedContour, symbolicGetContour, symbolicGetContourMesh) where
import Prelude(map, ($), (-), (/), (+), (>), (*), (.), reverse, cos, pi, sin, max, fromInteger, ceiling)
import Graphics.Implicit.Definitions (, 2, SymbolicObj2(RectR, Circle, Translate2, Scale2), Polyline, (*))
import Graphics.Implicit.Definitions (, 2, SymbolicObj2(RectR, Circle, Translate2, Scale2), Polyline(Polyline), Polytri(Polytri), (*))
import Graphics.Implicit.Export.MarchingSquaresFill (getContourMesh)
@ -29,37 +29,44 @@ symbolicGetOrientedContour :: -> SymbolicObj2 -> [Polyline]
symbolicGetOrientedContour res symbObj = map orient $ symbolicGetContour res symbObj
where
obj = getImplicit2 symbObj
-- FIXME: cowardly case handling.
orient :: Polyline -> Polyline
orient [] = []
orient [_] = []
orient points@(x:y:_) =
orient (Polyline (points@(x:y:_))) =
let
v = (\(a,b) -> (b, -a)) (y - x)
dv = v ^/ (magnitude v / res / 0.1)
in if obj (x + dv) - obj x > 0
then points
else reverse points
then Polyline points
else Polyline $ reverse points
orient (Polyline []) = Polyline []
orient (Polyline [_]) = Polyline []
symbolicGetContour :: -> SymbolicObj2 -> [Polyline]
symbolicGetContour _ (RectR 0 (x1,y1) (x2,y2)) = [[ (x1,y1), (x2,y1), (x2,y2), (x1,y2), (x1,y1) ]]
symbolicGetContour res (Circle r) = [[ ( r*cos(2*pi*m/n), r*sin(2*pi*m/n) ) | m <- [0.. n] ]] where
symbolicGetContour :: -> SymbolicObj2 -> [Polyline]
symbolicGetContour _ (RectR 0 (x1,y1) (x2,y2)) = [Polyline [ (x1,y1), (x2,y1), (x2,y2), (x1,y2), (x1,y1) ]]
-- FIXME: magic number.
symbolicGetContour res (Circle r) = [Polyline [ ( r*cos(2*pi*m/n), r*sin(2*pi*m/n) ) | m <- [0.. n] ]] where
n ::
n = max 5 (fromInteger . ceiling $ 2*pi*r/res)
symbolicGetContour res (Translate2 v obj) = map (map (+ v) ) $ symbolicGetContour res obj
symbolicGetContour res (Scale2 s@(a,b) obj) = map (map (* s)) $ symbolicGetContour (res/sc) obj
symbolicGetContour res (Translate2 v obj) = appOpPolylines (+ v) $ symbolicGetContour res obj
symbolicGetContour res (Scale2 s@(a,b) obj) = appOpPolylines (* s) $ symbolicGetContour (res/sc) obj
where sc = max a b
symbolicGetContour res obj = case rebound2 (getImplicit2 obj, getBox2 obj) of
(obj', (a,b)) -> Render.getContour a b res obj'
appOpPolylines :: (2 -> 2) -> [Polyline] -> [Polyline]
appOpPolylines op polylines = map (appOpPolyline op) polylines
appOpPolyline :: (2 -> 2) -> Polyline -> Polyline
appOpPolyline op (Polyline xs) = Polyline $ map op xs
symbolicGetContourMesh :: -> SymbolicObj2 -> [(2,2,2)]
symbolicGetContourMesh res (Translate2 v obj) = map (\(a,b,c) -> (a + v, b + v, c + v) ) $
symbolicGetContourMesh :: -> SymbolicObj2 -> [Polytri]
symbolicGetContourMesh res (Translate2 v obj) = map (\(Polytri (a,b,c)) -> (Polytri (a + v, b + v, c + v)) ) $
symbolicGetContourMesh res obj
symbolicGetContourMesh res (Scale2 s@(a,b) obj) = map (\(c,d,e) -> (c * s, d * s, e * s) ) $
symbolicGetContourMesh res (Scale2 s@(a,b) obj) = map (\(Polytri (c,d,e)) -> (Polytri (c * s, d * s, e * s)) ) $
symbolicGetContourMesh (res/sc) obj where sc = max a b
symbolicGetContourMesh _ (RectR 0 (x1,y1) (x2,y2)) = [((x1,y1), (x2,y1), (x2,y2)), ((x2,y2), (x1,y2), (x1,y1)) ]
symbolicGetContourMesh _ (RectR 0 (x1,y1) (x2,y2)) = [Polytri ((x1,y1), (x2,y1), (x2,y2)), Polytri ((x2,y2), (x1,y2), (x1,y1)) ]
-- FIXME: magic number.
symbolicGetContourMesh res (Circle r) =
[ ((0,0),
[ Polytri ((0,0),
(r*cos(2*pi*m/n), r*sin(2*pi*m/n)),
(r*cos(2*pi*(m+1)/n), r*sin(2*pi*(m+1)/n))
)| m <- [0.. n-1] ]
@ -68,5 +75,3 @@ symbolicGetContourMesh res (Circle r) =
n = max 5 (fromInteger . ceiling $ 2*pi*r/res)
symbolicGetContourMesh res obj = case rebound2 (getImplicit2 obj, getBox2 obj) of
(obj', (a,b)) -> getContourMesh a b (res,res) obj'

View File

@ -6,22 +6,24 @@
{-# LANGUAGE ExplicitForAll #-}
-- FIXME: why are these needed?
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances, FlexibleContexts, TypeSynonymInstances, UndecidableInstances #-}
{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FlexibleContexts, TypeSynonymInstances, UndecidableInstances #-}
-- The purpose of this function is to symbolicaly compute triangle meshes using the symbolic system where possible.
-- Otherwise we coerce it into an implicit function and apply our modified marching cubes algorithm.
module Graphics.Implicit.Export.SymbolicObj3 (symbolicGetMesh) where
import Prelude(map, zip, length, filter, (>), ($), null, concat, (++), concatMap)
import Prelude(map, zip, length, filter, (>), ($), null, (++), concatMap)
import Graphics.Implicit.Definitions (, 3, SymbolicObj3(UnionR3))
import Graphics.Implicit.Definitions (, 3, SymbolicObj3(UnionR3), Triangle, TriangleMesh(TriangleMesh))
import Graphics.Implicit.Export.Render (getMesh)
import Graphics.Implicit.ObjectUtil (getBox3, getImplicit3)
import Graphics.Implicit.MathUtil(box3sWithin)
import Graphics.Implicit.Export.Symbolic.Rebound3 (rebound3)
symbolicGetMesh :: -> SymbolicObj3 -> [(3, 3, 3)]
import Control.Arrow(first, second)
symbolicGetMesh :: -> SymbolicObj3 -> TriangleMesh
{--
-- A translated objects mesh is its mesh translated.
@ -195,32 +197,32 @@ symbolicGetMesh res (ExtrudeRM r twist scale translate obj2 h) =
map transformTriangle (side_tris ++ bottom_tris ++ top_tris)
-}
symbolicGetMesh res inputObj@(UnionR3 r objs) =
symbolicGetMesh res inputObj@(UnionR3 r objs) = TriangleMesh $
let
boxes = map getBox3 objs
boxedObjs = zip boxes objs
sepFree :: forall a. [((3, 3), a)] -> ([a], [a])
sepFree ((box,obj):others) =
sepFree ((box,obj):others) =
if length (filter (box3sWithin r box) boxes) > 1
then (\(a,b) -> (obj:a,b)) $ sepFree others
else (\(a,b) -> (a,obj:b)) $ sepFree others
then first ((:) obj) $ sepFree others
else second ((:) obj) $ sepFree others
sepFree [] = ([],[])
(dependants, independents) = sepFree boxedObjs
in if null independents
then case rebound3 (getImplicit3 inputObj, getBox3 inputObj) of
(obj, (a,b)) -> getMesh a b res obj
(obj, (a,b)) -> unmesh $ getMesh a b res obj
else if null dependants
then concatMap (symbolicGetMesh res) independents
else concatMap (symbolicGetMesh res) independents
++ concat [symbolicGetMesh res (UnionR3 r dependants)]
then concatMap unmesh $ map (symbolicGetMesh res) independents
else (concatMap unmesh $ map (symbolicGetMesh res) independents)
++ (unmesh $ symbolicGetMesh res (UnionR3 r dependants))
-- If all that fails, coerce and apply marching cubes :(
-- (rebound is for being safe about the bounding box --
-- it slightly streches it to make sure nothing will
-- have problems because it is right at the edge )
-- | If all that fails, coerce and apply marching cubes :(
symbolicGetMesh res obj =
case rebound3 (getImplicit3 obj, getBox3 obj) of
(obj', (a,b)) -> getMesh a b res obj'
-- Use rebound3 to stretch bounding box.
case rebound3 (getImplicit3 obj, getBox3 obj) of
(obj', (a,b)) -> getMesh a b res obj'
unmesh :: TriangleMesh -> [Triangle]
unmesh (TriangleMesh m) = m

View File

@ -5,51 +5,52 @@
-- This module exists to re-export a coherent set of functions to define
-- Data.Text.Lazy builders with.
module Graphics.Implicit.Export.TextBuilderUtils
(
-- Values from Data.Text.Lazy
Text,
pack,
-- Values from Data.Text.Lazy.Builder, as well as some special builders
Builder,
module Graphics.Implicit.Export.TextBuilderUtils (
-- From Data.Text.Lazy
module DTL,
-- From Data.Text.Lazy.Builder
module DTLB,
toLazyText,
fromLazyText,
buildInt,
-- Serialize a float in full precision
-- some special case Builders.
bf,
-- Serialize a float with four decimal places
buildTruncFloat,
build,
buildInt,
-- Values from Data.Monoid
(<>),
mconcat,
mempty
) where
import Prelude (Int, Maybe(Nothing, Just), ($))
import Prelude (Maybe(Nothing, Just), Int, ($))
import Data.Text.Lazy (Text, pack)
import Graphics.Implicit.Definitions (, , fromtoFloat)
import Data.Text.Lazy as DTL (Text, pack)
-- We manually redefine this operator to avoid a dependency on base >= 4.5
-- This will become unnecessary later.
import Data.Monoid (Monoid, mappend, mconcat, mempty)
import Data.Text.Internal.Lazy (defaultChunkSize)
import Data.Text.Lazy.Builder (Builder, toLazyTextWith, fromLazyText)
import Data.Text.Lazy.Builder as DTLB (Builder, toLazyTextWith, fromLazyText)
import Data.Text.Lazy.Builder.RealFloat (formatRealFloat, FPFormat(Exponent, Fixed))
import Data.Text.Lazy.Builder.Int (decimal)
import Graphics.Implicit.Definitions ()
-- The chunk size for toLazyText is very small (128 bytes), so we export
-- a version with a much larger size (~16 K)
toLazyText :: Builder -> Text
toLazyText = toLazyTextWith defaultChunkSize
bf, buildTruncFloat :: -> Builder
bf = formatRealFloat Exponent Nothing
-- | Serialize a float in full precision
bf :: -> Builder
bf value = formatRealFloat Exponent Nothing $ (fromtoFloat value)
-- | Serialize a float with four decimal places
buildTruncFloat :: -> Builder
buildTruncFloat = formatRealFloat Fixed $ Just 4
build :: -> Builder
build = decimal
buildInt :: Int -> Builder
buildInt = decimal

View File

@ -8,31 +8,78 @@
-- Make string litearls more polymorphic, so we can use them with Builder.
{-# LANGUAGE OverloadedStrings #-}
module Graphics.Implicit.Export.TriangleMeshFormats where
-- This module exposes three functions, which convert a triangle mesh to an output file.
module Graphics.Implicit.Export.TriangleMeshFormats (stl, binaryStl, jsTHREE) where
import Prelude (Real, Float, Int, ($), (+), map, (.), realToFrac, toEnum, length, zip, return)
import Prelude (Float, Eq, Bool, ($), (+), map, (.), toEnum, length, zip, return, (==), (||), (&&), filter, not)
import Graphics.Implicit.Definitions (Triangle, TriangleMesh, 3)
import Graphics.Implicit.Export.TextBuilderUtils (Text, Builder, toLazyText, (<>), bf, buildInt)
import Graphics.Implicit.Definitions (Triangle(Triangle), TriangleMesh(TriangleMesh), , 3, , fromtoFloat)
import Graphics.Implicit.Export.TextBuilderUtils (Text, Builder, toLazyText, (<>), bf, build)
import Blaze.ByteString.Builder (Write, writeStorable, toLazyByteString, fromByteString, fromWord32le, fromWord16le, fromWrite)
import qualified Data.ByteString.Builder.Internal as BI (Builder)
-- note: moved to prelude in newer version
import Data.Monoid(mconcat)
import Data.ByteString (replicate)
import Data.ByteString.Lazy (ByteString)
import Data.Storable.Endian (LittleEndian(LE))
import Data.VectorSpace (normalized, negateV)
import Data.VectorSpace (normalized, (^-^))
import Data.Cross (cross3)
unmesh :: TriangleMesh -> [Triangle]
unmesh (TriangleMesh m) = m
normal :: (3,3,3) -> 3
normal (a,b,c) =
normalized $ (b + negateV a) `cross3` (c + negateV a)
normalized $ (b ^-^ a) `cross3` (c ^-^ a)
stl :: [Triangle] -> Text
stl triangles = toLazyText $ stlHeader <> mconcat (map triangle triangles) <> stlFooter
-- | Removes triangles that are empty when converting their positions to Float resolution.
cleanupTris :: TriangleMesh -> TriangleMesh
cleanupTris tris =
let
floatPoint :: (, , ) -> (Float, Float, Float)
floatPoint (a,b,c) = (toFloat a, toFloat b, toFloat c)
{-
-- Alternate methods of detecting degenerate triangles -- not used.
-- If you have to use one of these, please tell the maintainer.
-- | Does this triangle fail because it's points are on the same line?
isDegenerateTriLine (p1,p2,p3) = (norm (p1,p2)) == (norm (p2,p3)) || (norm (p1,p3)) == (norm(p2,p3))
where
norm :: ((Float,Float,Float),(Float,Float,Float)) -> (Float,Float,Float)
norm (begin, end) = normalized $ begin ^-^ end
-- | Does this triangle fail because of two of it's points overlap?
isDegenerateTriPoint :: Eq t => (t,t,t) -> Bool
isDegenerateTriPoint (a,b,c) = (a == b) || (b == c) || (a == c)
-}
-- | Does this triangle fail because it is constrained on two axises?
isDegenerateTri2Axis :: forall a. (Eq a) => ((a, a, a),(a, a, a),(a, a, a)) -> Bool
isDegenerateTri2Axis tri = ((ysame tri) && (xsame tri)) || ((zsame tri) && (ysame tri)) || ((zsame tri) && (xsame tri))
where
same :: forall a. Eq a => (a, a, a) -> Bool
same (n1, n2, n3) = n1 == n2 && n2 == n3
xsame :: forall a. Eq a => ((a, a, a), (a, a, a), (a, a, a)) -> Bool
xsame ((x1,_,_),(x2,_,_),(x3,_,_)) = same (x1, x2, x3)
ysame :: forall a. Eq a => ((a, a, a), (a, a, a), (a, a, a)) -> Bool
ysame ((_,y1,_),(_,y2,_),(_,y3,_)) = same (y1, y2, y3)
zsame :: forall a. Eq a => ((a, a, a), (a, a, a), (a, a, a)) -> Bool
zsame ((_,_,z1),(_,_,z2),(_,_,z3)) = same (z1, z2, z3)
isDegenerateTri :: Triangle -> Bool
isDegenerateTri (Triangle (a, b, c)) = (isDegenerateTri2Axis $ floatTri) -- || (isDegenerateTriLine $ floatTri) || (isDegenerateTriPoint $ floatTri)
where
floatTri = (floatPoint a, floatPoint b, floatPoint c)
in TriangleMesh $ filter (not . isDegenerateTri) (unmesh tris)
-- | Generate an STL file is ASCII format.
stl :: TriangleMesh -> Text
stl triangles = toLazyText $ stlHeader <> mconcat (map triangle $ unmesh $ cleanupTris triangles) <> stlFooter
where
stlHeader :: Builder
stlHeader = "solid ImplictCADExport\n"
@ -42,8 +89,8 @@ stl triangles = toLazyText $ stlHeader <> mconcat (map triangle triangles) <> st
vector (x,y,z) = bf x <> " " <> bf y <> " " <> bf z
vertex :: 3 -> Builder
vertex v = "vertex " <> vector v
triangle :: (3, 3, 3) -> Builder
triangle (a,b,c) =
triangle :: Triangle -> Builder
triangle (Triangle (a,b,c)) =
"facet normal " <> vector (normal (a,b,c)) <> "\n"
<> "outer loop\n"
<> vertex a <> "\n"
@ -51,25 +98,23 @@ stl triangles = toLazyText $ stlHeader <> mconcat (map triangle triangles) <> st
<> vertex c
<> "\nendloop\nendfacet\n"
-- | convert from to Float.
toFloat :: -> Float
toFloat = fromtoFloat
-- Write a 32-bit little-endian float to a buffer.
-- convert Floats and Doubles to Float.
toFloat :: Real a => a -> Float
toFloat = realToFrac :: (Real a) => a -> Float
-- | Write a 32-bit little-endian float to a buffer.
float32LE :: Float -> Write
float32LE = writeStorable . LE
binaryStl :: [Triangle] -> ByteString
binaryStl triangles = toLazyByteString $ header <> lengthField <> mconcat (map triangle triangles)
-- | Generate an STL file in it's binary format.
binaryStl :: TriangleMesh -> ByteString
binaryStl triangles = toLazyByteString $ header <> lengthField <> mconcat (map triangle $ unmesh $ cleanupTris triangles)
where header = fromByteString $ replicate 80 0
lengthField = fromWord32le $ toEnum $ length triangles
triangle (a,b,c) = normalV (a,b,c) <> point a <> point b <> point c <> fromWord16le 0
point :: forall a a1 a2. (Real a2, Real a1, Real a) => (a, a1, a2) -> BI.Builder
lengthField = fromWord32le $ toEnum $ length $ unmesh $ cleanupTris triangles
triangle (Triangle (a,b,c)) = normalV (a,b,c) <> point a <> point b <> point c <> fromWord16le 0
point :: (3) -> BI.Builder
point (x,y,z) = fromWrite $ float32LE (toFloat x) <> float32LE (toFloat y) <> float32LE (toFloat z)
normalV ps = let (x,y,z) = normal ps
in fromWrite $ float32LE (toFloat x) <> float32LE (toFloat y) <> float32LE (toFloat z)
normalV ps = point $ normal ps
jsTHREE :: TriangleMesh -> Text
jsTHREE triangles = toLazyText $ header <> vertcode <> facecode <> footer
@ -94,18 +139,18 @@ jsTHREE triangles = toLazyText $ header <> vertcode <> facecode <> footer
v :: 3 -> Builder
v (x,y,z) = "v(" <> bf x <> "," <> bf y <> "," <> bf z <> ");\n"
-- A face line
f :: Int -> Int -> Int -> Builder
f posa posb posc =
"f(" <> buildInt posa <> "," <> buildInt posb <> "," <> buildInt posc <> ");"
f :: -> -> -> Builder
f posa posb posc =
"f(" <> build posa <> "," <> build posb <> "," <> build posc <> ");"
verts = do
-- extract the vertices for each triangle
-- recall that a normed triangle is of the form ((vert, norm), ...)
(a,b,c) <- triangles
(Triangle (a,b,c)) <- unmesh $ cleanupTris triangles
-- The vertices from each triangle take up 3 position in the resulting list
[a,b,c]
vertcode = mconcat $ map v verts
facecode = mconcat $ do
(n,_) <- zip [0, 3 ..] triangles
(n,_) <- zip [0, 3 ..] $ unmesh $ cleanupTris triangles
let
(posa, posb, posc) = (n, n+1, n+2) :: (Int, Int, Int)
(posa, posb, posc) = (n, n+1, n+2) :: (, , )
return $ f posa posb posc

View File

@ -3,7 +3,7 @@
-- Released under the GNU AGPLV3+, see LICENSE
-- FIXME: why are these needed?
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances, FlexibleContexts, TypeSynonymInstances, UndecidableInstances #-}
{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FlexibleContexts, TypeSynonymInstances, UndecidableInstances #-}
-- Functions to make meshes/polylines finer.
@ -11,13 +11,13 @@ module Graphics.Implicit.Export.Util (normTriangle, normVertex, centroid) where
import Prelude(Fractional, (/), (-), ($), foldl, recip, realToFrac, length)
import Graphics.Implicit.Definitions (, 3, Obj3, Triangle, NormedTriangle)
import Graphics.Implicit.Definitions (, 3, Obj3, Triangle(Triangle), NormedTriangle(NormedTriangle))
import Data.VectorSpace (VectorSpace, Scalar, (^+^), (*^), (^/), (^-^), normalized, zeroV)
normTriangle :: -> Obj3 -> Triangle -> NormedTriangle
normTriangle res obj (a,b,c) =
(normify a', normify b', normify c')
normTriangle res obj (Triangle (a,b,c)) =
NormedTriangle (normify a', normify b', normify c')
where
normify = normVertex res obj
a' = (a ^+^ r*^b ^+^ r*^c) ^/ 1.02
@ -46,6 +46,7 @@ centroid pts =
where
norm :: Fractional a => a
norm = recip $ realToFrac $ length pts
{-# INLINABLE centroid #-}
{--- If we need to make a 2D mesh finer...
divideMesh2To :: -> [(2, 2, 2)] -> [(2, 2, 2)]

View File

@ -5,38 +5,39 @@
-- FIXME: why are these required?
{-# LANGUAGE FlexibleContexts, ScopedTypeVariables #-}
-- We'd like to parse openscad code, with some improvements, for backwards compatability.
-- An executor, which parses openscad code, and executes it.
module Graphics.Implicit.ExtOpenScad (runOpenscad) where
import Prelude(Char, Either(Left, Right), IO, ($), fmap)
import Prelude(String, Either(Left, Right), IO, ($), fmap)
import Graphics.Implicit.Definitions (SymbolicObj2, SymbolicObj3)
import Graphics.Implicit.ExtOpenScad.Definitions (VarLookup, OVal)
import Graphics.Implicit.ExtOpenScad.Definitions (VarLookup)
import Graphics.Implicit.ExtOpenScad.Parser.Statement (parseProgram)
import Graphics.Implicit.ExtOpenScad.Eval.Statement (runStatementI)
import Graphics.Implicit.ExtOpenScad.Default (defaultObjects)
import Graphics.Implicit.ExtOpenScad.Util.StateC (CompState(CompState))
import Graphics.Implicit.ExtOpenScad.Util.OVal (divideObjs)
import qualified Text.Parsec.Error as Parsec (ParseError)
import qualified Control.Monad as Monad (mapM_)
import qualified Control.Monad.State as State (runStateT)
import qualified System.Directory as Dir (getCurrentDirectory)
-- Small wrapper to handle parse errors, etc.
runOpenscad :: [Char] -> Either Parsec.ParseError (IO (VarLookup, [SymbolicObj2], [SymbolicObj3]))
runOpenscad s =
import Text.Parsec.Error (ParseError)
import Control.Monad (mapM_)
import Control.Monad.State (runStateT)
import System.Directory (getCurrentDirectory)
-- | Small wrapper of our parser to handle parse errors, etc.
runOpenscad :: String -> Either ParseError (IO (VarLookup, [SymbolicObj2], [SymbolicObj3]))
runOpenscad source =
let
initial = defaultObjects
rearrange :: forall t t1 t2 t3 t4. (t, (t4, [OVal], t1, t2, t3)) -> (t4, [SymbolicObj2], [SymbolicObj3])
rearrange (_, (varlookup, ovals, _ , _ , _)) = (varlookup, obj2s, obj3s) where
rearrange :: forall t. (t, CompState) -> (VarLookup, [SymbolicObj2], [SymbolicObj3])
rearrange (_, (CompState (varlookup, ovals, _))) = (varlookup, obj2s, obj3s) where
(obj2s, obj3s, _ ) = divideObjs ovals
in case parseProgram "" s of
in case parseProgram source of
Left e -> Left e
Right sts -> Right
$ fmap rearrange
$ (\sts' -> do
path <- Dir.getCurrentDirectory
State.runStateT sts' (initial, [], path, (), () )
path <- getCurrentDirectory
runStateT sts' $ CompState (initial, [], path)
)
$ Monad.mapM_ runStatementI sts
$ mapM_ runStatementI sts

View File

@ -5,18 +5,20 @@
-- Allow us to use explicit foralls when writing function type declarations.
{-# LANGUAGE ExplicitForAll #-}
-- We'd like to parse openscad code, with some improvements, for backwards compatability.
-- We'd like to parse openscad-ish code, with some improvements, for backwards compatability.
module Graphics.Implicit.ExtOpenScad.Default where
module Graphics.Implicit.ExtOpenScad.Default (defaultObjects) where
-- be explicit about where we pull things in from.
import Prelude (String, Bool(True, False), Maybe(Just, Nothing), ($), (++), map, pi, sin, cos, tan, asin, acos, atan, sinh, cosh, tanh, abs, signum, fromInteger, (.), floor, ceiling, round, exp, log, sqrt, max, min, atan2, (**), flip, (<), (>), (<=), (>=), (==), (/=), (&&), (||), not, show, foldl, (*), (/), mod, (+), zipWith, (-), otherwise)
import Prelude (Char, String, Bool(True, False), Maybe(Just, Nothing), Int, ($), (++), map, pi, sin, cos, tan, asin, acos, atan, sinh, cosh, tanh, abs, signum, fromInteger, (.), floor, ceiling, round, exp, log, sqrt, max, min, atan2, (**), flip, (<), (>), (<=), (>=), (==), (/=), (&&), (||), not, show, foldl, (*), (/), mod, (+), zipWith, (-), (!!), length, otherwise, fromIntegral)
import Graphics.Implicit.Definitions ()
import Graphics.Implicit.Definitions (, )
import Graphics.Implicit.ExtOpenScad.Definitions(VarLookup, OVal(OList, ONum, OString, OUndefined, OError, OModule, OFunc))
import Graphics.Implicit.ExtOpenScad.Util.OVal (toOObj, oTypeStr)
import Graphics.Implicit.ExtOpenScad.Primitives (primitives)
import Data.Map (fromList)
import Data.List (genericIndex, genericLength)
import Control.Arrow (second)
defaultObjects :: VarLookup -- = Map String OVal
defaultObjects = fromList $
@ -30,11 +32,11 @@ defaultObjects = fromList $
-- Missing standard ones:
-- rand, lookup,
defaultConstants :: [([Char], OVal)]
defaultConstants :: [(String, OVal)]
defaultConstants = map (\(a,b) -> (a, toOObj (b::) ))
[("pi", pi)]
defaultFunctions :: [([Char], OVal)]
defaultFunctions :: [(String, OVal)]
defaultFunctions = map (\(a,b) -> (a, toOObj ( b :: -> )))
[
("sin", sin),
@ -58,7 +60,7 @@ defaultFunctions = map (\(a,b) -> (a, toOObj ( b :: -> )))
("sqrt", sqrt)
]
defaultFunctions2 :: [([Char], OVal)]
defaultFunctions2 :: [(String, OVal)]
defaultFunctions2 = map (\(a,b) -> (a, toOObj (b :: -> -> ) ))
[
("max", max),
@ -67,25 +69,21 @@ defaultFunctions2 = map (\(a,b) -> (a, toOObj (b :: -> -> ) ))
("pow", (**))
]
defaultFunctionsSpecial :: [([Char], OVal)]
defaultFunctionsSpecial :: [(String, OVal)]
defaultFunctionsSpecial =
[
("map", toOObj $ flip $
("map", toOObj $ flip
(map :: (OVal -> OVal) -> [OVal] -> [OVal] )
)
]
defaultModules :: [(String, OVal)]
defaultModules =
map (\(a,b) -> (a, OModule b)) primitives
map (second OModule) primitives
-- more complicated ones:
defaultPolymorphicFunctions :: [([Char], OVal)]
defaultPolymorphicFunctions :: [(String, OVal)]
defaultPolymorphicFunctions =
[
("+", sumtotal),
@ -138,7 +136,7 @@ defaultPolymorphicFunctions =
div' (OList a) (ONum b) = OList (map (\x -> div' x (ONum b)) a)
div' a b = errorAsAppropriate "divide" a b
omod (ONum a) (ONum b) = ONum $ fromInteger $ mod (floor a) (floor b)
omod (ONum a) (ONum b) = ONum . fromInteger $ mod (floor a) (floor b)
omod a b = errorAsAppropriate "modulo" a b
append (OList a) (OList b) = OList $ a++b
@ -175,44 +173,46 @@ defaultPolymorphicFunctions =
index (OList l) (ONum ind) =
let
n :: Int
n ::
n = floor ind
in
if n < length l then l !! n else OError ["List accessd out of bounds"]
if n < genericLength l then l `genericIndex` n else OError ["List accessd out of bounds"]
index (OString s) (ONum ind) =
let
n :: Int
n ::
n = floor ind
in if n < length s then OString [s !! n] else OError ["List accessd out of bounds"]
in if n < genericLength s then OString [s `genericIndex` n] else OError ["List accessd out of bounds"]
index a b = errorAsAppropriate "index" a b
osplice (OList list) (ONum a) ( ONum b ) =
OList $ splice list (floor a) (floor b)
osplice (OString str) (ONum a) ( ONum b ) =
OString $ splice str (floor a) (floor b)
osplice (OList list) (OUndefined) (ONum b ) =
osplice (OList list) OUndefined (ONum b ) =
OList $ splice list 0 (floor b)
osplice (OString str) (OUndefined) (ONum b ) =
osplice (OString str) OUndefined (ONum b ) =
OString $ splice str 0 (floor b)
osplice (OList list) (ONum a) ( OUndefined) =
OList $ splice list (floor a) (length list + 1)
osplice (OString str) (ONum a) ( OUndefined) =
OString $ splice str (floor a) (length str + 1)
osplice (OList list) (OUndefined) (OUndefined) =
OList $ splice list 0 (length list + 1)
osplice (OString str) (OUndefined) (OUndefined) =
OString $ splice str 0 (length str + 1)
osplice (OList list) (ONum a) OUndefined =
OList $ splice list (floor a) (genericLength list + 1)
osplice (OString str) (ONum a) OUndefined =
OString $ splice str (floor a) (genericLength str + 1)
osplice (OList list) OUndefined OUndefined =
OList $ splice list 0 (genericLength list + 1)
osplice (OString str) OUndefined OUndefined =
OString $ splice str 0 (genericLength str + 1)
osplice _ _ _ = OUndefined
splice :: [a] -> Int -> Int -> [a]
splice :: [a] -> -> -> [a]
splice [] _ _ = []
splice (l@(x:xs)) a b
| a < 0 = splice l (a+n) b
| b < 0 = splice l a (b+n)
| a > 0 = splice xs (a-1) (b-1)
| b > 0 = x:(splice xs a (b-1) )
| b > 0 = x: splice xs a (b-1)
| otherwise = []
where n = length l
where
n ::
n = genericLength l
errorAsAppropriate _ err@(OError _) _ = err
errorAsAppropriate _ _ err@(OError _) = err
@ -220,24 +220,24 @@ defaultPolymorphicFunctions =
["Can't " ++ name ++ " objects of types " ++ oTypeStr a ++ " and " ++ oTypeStr b ++ "."]
list_gen :: [] -> Maybe []
list_gen [a,b] = Just [fromInteger (ceiling a).. fromInteger (floor b)]
list_gen [a, b] = Just $ map fromInteger $ [(ceiling a).. (floor b)]
list_gen [a, b, c] =
let
nr = (c-a)/b
n ::
n = fromInteger (floor nr)
in if nr - n > 0
then Just
[fromInteger (ceiling a), fromInteger (ceiling (a+b)).. fromInteger (floor (c - b*(nr -n)))]
else Just
[fromInteger (ceiling a), fromInteger (ceiling (a+b)).. fromInteger (floor c)]
then Just $ map fromInteger $
[(ceiling a), (ceiling (a+b)).. (floor (c - b*(nr -n)))]
else Just $ map fromInteger $
[(ceiling a), (ceiling (a+b)).. (floor c)]
list_gen _ = Nothing
ternary :: forall t. Bool -> t -> t -> t
ternary True a _ = a
ternary False _ b = b
olength (OString s) = ONum $ fromIntegral $ length s
olength (OList s) = ONum $ fromIntegral $ length s
olength (OString s) = ONum $ genericLength s
olength (OList s) = ONum $ genericLength s
olength a = OError ["Can't take length of a " ++ oTypeStr a ++ "."]

View File

@ -14,14 +14,18 @@ module Graphics.Implicit.ExtOpenScad.Definitions (ArgParser(AP, APTest, APBranch
TestInvariant(EulerCharacteristic),
collector) where
import Prelude(Eq, Show, String, Maybe, Bool(True, False), Int, IO, (==), show, map, ($), (++), undefined, all, id, zipWith, foldl1)
import Prelude(Eq, Show, String, Maybe, Bool(True, False), IO, (==), show, map, ($), (++), undefined, and, zipWith, foldl1)
import Graphics.Implicit.Definitions (, SymbolicObj2, SymbolicObj3)
-- Resolution of the world, Integer type, and symbolic languages for 2D and 3D objects.
import Graphics.Implicit.Definitions (, , SymbolicObj2, SymbolicObj3)
import Control.Applicative (Applicative, Alternative((<|>), empty), pure, (<*>))
import Control.Monad (Functor, Monad, fmap, (>>=), mzero, mplus, MonadPlus, liftM, ap, return)
import Control.Monad (Functor, Monad, fmap, (>>=), mzero, mplus, MonadPlus, liftM, ap, return, (>=>))
import Data.Map (Map)
-- for keeping track of the line and column number we are on in our extopenscad file.
import Text.ParserCombinators.Parsec (Line, Column)
-----------------------------------------------------------------
-- | Handles parsing arguments to modules
data ArgParser a
@ -45,14 +49,14 @@ instance Functor ArgParser where
fmap = liftM
instance Applicative ArgParser where
pure a = APTerminator a
pure = APTerminator
(<*>) = ap
instance Monad ArgParser where
-- We need to describe how (>>=) works.
-- Let's get the hard ones out of the way first.
-- ArgParser actually
(AP str fallback d f) >>= g = AP str fallback d (\a -> (f a) >>= g)
(AP str fallback d f) >>= g = AP str fallback d (f >=> g)
(APFailIf b errmsg child) >>= g = APFailIf b errmsg (child >>= g)
-- These next to is easy, they just pass the work along to their child
(APExample str child) >>= g = APExample str (child >>= g)
@ -60,13 +64,13 @@ instance Monad ArgParser where
-- And an ArgParserTerminator happily gives away the value it contains
(APTerminator a) >>= g = g a
(APBranch bs) >>= g = APBranch $ map (>>= g) bs
return g = APTerminator g
return = pure
instance MonadPlus ArgParser where
mzero = APFailIf True "" undefined
mplus (APBranch as) (APBranch bs) = APBranch ( as ++ bs )
mplus (APBranch as) b = APBranch ( as ++ [b] )
mplus a (APBranch bs) = APBranch ( [a] ++ bs )
mplus a (APBranch bs) = APBranch ( a : bs )
mplus a b = APBranch [ a , b ]
instance Alternative ArgParser where
@ -75,8 +79,8 @@ instance Alternative ArgParser where
type Symbol = String
data Pattern = Name Symbol
| ListP [Pattern]
data Pattern = Name Symbol
| ListP [Pattern]
| Wild
| Symbol :@ Pattern
deriving (Show, Eq)
@ -88,7 +92,8 @@ data Expr = Var Symbol
| Expr :$ [Expr]
deriving (Show, Eq)
data StatementI = StatementI Int (Statement StatementI)
-- a statement, along with the line and column number it is found on.
data StatementI = StatementI Line Column (Statement StatementI)
deriving (Show, Eq)
data Statement st = Include String Bool
@ -118,7 +123,7 @@ data OVal = OUndefined
instance Eq OVal where
(OBool a) == (OBool b) = a == b
(ONum a) == (ONum b) = a == b
(OList a) == (OList b) = all id $ zipWith (==) a b
(OList a) == (OList b) = and $ zipWith (==) a b
(OString a) == (OString b) = a == b
_ == _ = False
@ -141,6 +146,6 @@ collector :: Symbol -> [Expr] -> Expr
collector _ [x] = x
collector s l = Var s :$ [ListE l]
data TestInvariant = EulerCharacteristic Int
newtype TestInvariant = EulerCharacteristic
deriving (Show)

View File

@ -4,7 +4,7 @@
module Graphics.Implicit.ExtOpenScad.Eval.Expr (evalExpr, matchPat) where
import Prelude (String, Maybe(Just, Nothing), IO, concat, ($), map, return, zip, (==), (!!), const, (++), foldr, concatMap)
import Prelude (String, Maybe(Just, Nothing), IO, concat, ($), map, return, zip, (!!), const, (++), foldr, concatMap)
import Graphics.Implicit.ExtOpenScad.Definitions (
Pattern(Name, ListP, Wild),
@ -15,11 +15,12 @@ import Graphics.Implicit.ExtOpenScad.Definitions (
import Graphics.Implicit.ExtOpenScad.Util.OVal (oTypeStr, getErrors)
import Graphics.Implicit.ExtOpenScad.Util.StateC (StateC, getVarLookup)
import Data.List (findIndex)
import Data.List (elemIndex)
import Data.Map (fromList, lookup)
import Control.Monad (zipWithM, mapM, forM)
import Control.Monad.State (StateT, get, modify, liftIO, runStateT)
import Control.Arrow (second)
patVars :: Pattern -> [String]
patVars (Name name) = [name]
@ -40,22 +41,19 @@ matchPat pat val = do
vals <- patMatch pat val
return $ fromList $ zip vars vals
evalExpr :: Expr -> StateC OVal
evalExpr expr = do
varlookup <- getVarLookup
(valf, _) <- liftIO $ runStateT (evalExpr' expr) (varlookup, [])
return $ valf []
evalExpr' :: Expr -> StateT (VarLookup, [String]) IO ([OVal] -> OVal)
evalExpr' (Var name ) = do
(varlookup, namestack) <- get
return $
case (lookup name varlookup, findIndex (==name) namestack) of
(_, Just pos) -> \s -> s !! pos
case (lookup name varlookup, elemIndex name namestack) of
(_, Just pos) -> (!! pos)
(Just val, _) -> const val
_ -> const $ OError ["Variable " ++ name ++ " not in scope" ]
@ -80,7 +78,7 @@ evalExpr' (fexpr :$ argExprs) = do
evalExpr' (LamE pats fexpr) = do
fparts <- forM pats $ \pat -> do
modify (\(vl, names) -> (vl, patVars pat ++ names))
modify (second (patVars pat ++))
return $ \f xss -> OFunc $ \val -> case patMatch pat val of
Just xs -> f (xs ++ xss)
Nothing -> OError ["Pattern match failed"]

View File

@ -2,9 +2,10 @@
-- Copyright 2014 2015 2016, Julia Longtin (julial@turinglace.com)
-- Released under the GNU AGPLV3+, see LICENSE
{-# LANGUAGE ViewPatterns, ScopedTypeVariables #-}
-- FIXME: why is this required?
{-# LANGUAGE ScopedTypeVariables #-}
module Graphics.Implicit.ExtOpenScad.Eval.Statement where
module Graphics.Implicit.ExtOpenScad.Eval.Statement (runStatementI) where
import Prelude(Maybe(Just, Nothing), Bool(True, False), Either(Left, Right), FilePath, IO, (.), ($), show, putStrLn, concatMap, return, (++), fmap, reverse, fst, readFile)
@ -19,63 +20,68 @@ import Graphics.Implicit.ExtOpenScad.Definitions (
import Graphics.Implicit.ExtOpenScad.Util.OVal (getErrors)
import Graphics.Implicit.ExtOpenScad.Util.ArgParser (argument, defaultTo, argMap)
import Graphics.Implicit.ExtOpenScad.Util.StateC (StateC, errorC, modifyVarLookup, mapMaybeM, lookupVar, pushVals, getRelPath, withPathShiftedBy, getVals, putVals)
import Graphics.Implicit.ExtOpenScad.Util.StateC (StateC, CompState(CompState), errorC, modifyVarLookup, mapMaybeM, lookupVar, pushVals, getRelPath, withPathShiftedBy, getVals, putVals)
import Graphics.Implicit.ExtOpenScad.Eval.Expr (evalExpr, matchPat)
import Graphics.Implicit.ExtOpenScad.Parser.Statement (parseProgram)
import qualified Data.Map as Map
import qualified Control.Monad as Monad
import Data.Maybe(fromMaybe)
import Data.Map (union, fromList)
import Control.Monad (forM_, forM, mapM_)
import Control.Monad.State (get, liftIO, mapM, runStateT, (>>))
import qualified System.FilePath as FilePath
import System.FilePath (takeDirectory)
-- Run statements out of the OpenScad file.
runStatementI :: StatementI -> StateC ()
runStatementI (StatementI lineN (pat := expr)) = do
runStatementI (StatementI lineN columnN (pat := expr)) = do
val <- evalExpr expr
let posMatch = matchPat pat val
case (getErrors val, posMatch) of
(Just err, _ ) -> errorC lineN err
(_, Just match) -> modifyVarLookup $ Map.union match
(_, Nothing ) -> errorC lineN "pattern match failed in assignment"
(Just err, _ ) -> errorC lineN columnN err
(_, Just match) -> modifyVarLookup $ union match
(_, Nothing ) -> errorC lineN columnN "pattern match failed in assignment"
runStatementI (StatementI lineN (Echo exprs)) = do
runStatementI (StatementI lineN columnN (Echo exprs)) = do
let
show2 (OString s) = s
show2 x = show x
vals <- mapM evalExpr exprs
case getErrors (OList vals) of
Nothing -> liftIO . putStrLn $ concatMap show2 vals
Just err -> errorC lineN err
Just err -> errorC lineN columnN err
runStatementI (StatementI lineN (For pat expr loopContent)) = do
runStatementI (StatementI lineN columnN (For pat expr loopContent)) = do
val <- evalExpr expr
case (getErrors val, val) of
(Just err, _) -> errorC lineN err
(_, OList vals) -> Monad.forM_ vals $ \v ->
(Just err, _) -> errorC lineN columnN err
(_, OList vals) -> forM_ vals $ \v ->
case matchPat pat v of
Just match -> do
modifyVarLookup $ Map.union match
modifyVarLookup $ union match
runSuite loopContent
Nothing -> return ()
_ -> return ()
runStatementI (StatementI lineN (If expr a b)) = do
runStatementI (StatementI lineN columnN (If expr a b)) = do
val <- evalExpr expr
case (getErrors val, val) of
(Just err, _ ) -> errorC lineN ("In conditional expression of if statement: " ++ err)
(Just err, _ ) -> errorC lineN columnN ("In conditional expression of if statement: " ++ err)
(_, OBool True ) -> runSuite a
(_, OBool False) -> runSuite b
_ -> return ()
runStatementI (StatementI lineN (NewModule name argTemplate suite)) = do
argTemplate' <- Monad.forM argTemplate $ \(name', defexpr) -> do
runStatementI (StatementI lineN columnN (NewModule name argTemplate suite)) = do
argTemplate' <- forM argTemplate $ \(name', defexpr) -> do
defval <- mapMaybeM evalExpr defexpr
return (name', defval)
(varlookup, _, path, _, _) <- get
(CompState (varlookup, _, path)) <- get
-- FIXME: \_? really?
runStatementI $ StatementI lineN $ (Name name :=) $ LitE $ OModule $ \_ -> do
newNameVals <- Monad.forM argTemplate' $ \(name', maybeDef) -> do
runStatementI . StatementI lineN columnN $ (Name name :=) $ LitE $ OModule $ \_ -> do
newNameVals <- forM argTemplate' $ \(name', maybeDef) -> do
val <- case maybeDef of
Just def -> argument name' `defaultTo` def
Nothing -> argument name'
@ -97,57 +103,53 @@ runStatementI (StatementI lineN (NewModule name argTemplate suite)) = do
_ -> OUndefined
newNameVals' = newNameVals ++ [("children", children),("child", child), ("childBox", childBox)]
-}
varlookup' = Map.union (Map.fromList newNameVals) varlookup
varlookup' = union (fromList newNameVals) varlookup
suiteVals = runSuiteCapture varlookup' path suite
return suiteVals
runStatementI (StatementI lineN (ModuleCall name argsExpr suite)) = do
runStatementI (StatementI lineN columnN (ModuleCall name argsExpr suite)) = do
maybeMod <- lookupVar name
(varlookup, _, path, _, _) <- get
(CompState (varlookup, _, path)) <- get
childVals <- fmap reverse . liftIO $ runSuiteCapture varlookup path suite
argsVal <- Monad.forM argsExpr $ \(posName, expr) -> do
argsVal <- forM argsExpr $ \(posName, expr) -> do
val <- evalExpr expr
return (posName, val)
newVals <- case maybeMod of
Just (OModule mod') -> liftIO ioNewVals where
argparser = mod' childVals
ioNewVals = case fst $ argMap argsVal argparser of
Just iovals -> iovals
Nothing -> return []
ioNewVals = fromMaybe (return []) (fst $ argMap argsVal argparser)
Just foo -> do
case getErrors foo of
Just err -> errorC lineN err
Nothing -> errorC lineN $ "Object called not module!"
Just err -> errorC lineN columnN err
Nothing -> errorC lineN columnN "Object called not module!"
return []
Nothing -> do
errorC lineN $ "Module " ++ name ++ " not in scope."
errorC lineN columnN $ "Module " ++ name ++ " not in scope."
return []
pushVals newVals
runStatementI (StatementI _ (Include name injectVals)) = do
runStatementI (StatementI _ _ (Include name injectVals)) = do
name' <- getRelPath name
content <- liftIO $ readFile name'
case parseProgram name content of
case parseProgram content of
Left e -> liftIO $ putStrLn $ "Error parsing " ++ name ++ ":" ++ show e
Right sts -> withPathShiftedBy (FilePath.takeDirectory name) $ do
Right sts -> withPathShiftedBy (takeDirectory name) $ do
vals <- getVals
putVals []
runSuite sts
vals' <- getVals
if injectVals then putVals (vals' ++ vals) else putVals vals
runStatementI (StatementI _ DoNothing) = do
liftIO $ putStrLn $ "Do Nothing?"
runStatementI (StatementI _ _ DoNothing) = liftIO $ putStrLn "Do Nothing?"
runSuite :: [StatementI] -> StateC ()
runSuite stmts = Monad.mapM_ runStatementI stmts
runSuite = mapM_ runStatementI
runSuiteCapture :: VarLookup -> FilePath -> [StatementI] -> IO [OVal]
runSuiteCapture varlookup path suite = do
(res, _) <- runStateT
(runSuite suite >> getVals)
(varlookup, [], path, (), () )
(CompState (varlookup, [], path))
return res

View File

@ -2,17 +2,16 @@
-- Copyright 2014 2015 2016, Julia Longtin (julial@turinglace.com)
-- Released under the GNU AGPLV3+, see LICENSE
module Graphics.Implicit.ExtOpenScad.Parser.Expr where
-- a parser for a numeric expression.
module Graphics.Implicit.ExtOpenScad.Parser.Expr(expr0) where
import Prelude (Char, Maybe(Nothing, Just), fmap, ($), (>>), return, Bool(True, False), read, (++), id, foldl, map, foldl1, unzip, tail, zipWith3)
-- the datatype representing the graininess of our world.
import Graphics.Implicit.Definitions ()
import Prelude (Char, Maybe(Nothing, Just), fmap, ($), (.), (>>), return, Bool(True, False), read, (++), (*), (**), (/), id, foldl, map, foldl1, unzip, tail, zipWith3, foldr)
-- The parsec parsing library.
import Text.ParserCombinators.Parsec (GenParser, string, many1, digit, char, many, noneOf, sepBy, sepBy1, optionMaybe, try)
import Graphics.Implicit.ExtOpenScad.Definitions (Expr(Var, LitE, ListE, (:$)), OVal(ONum, OString, OBool, OUndefined), collector)
import Graphics.Implicit.ExtOpenScad.Definitions (Expr(Var, LamE, LitE, ListE, (:$)), OVal(ONum, OString, OBool, OUndefined), collector, Pattern(Name))
import Graphics.Implicit.ExtOpenScad.Parser.Util (variableSymb, (?:), (*<|>), genSpace, padString)
variable :: GenParser Char st Expr
@ -23,27 +22,87 @@ literal = ("literal" ?:) $
"boolean" ?: do
b <- (string "true" >> return True )
*<|> (string "false" >> return False)
return $ LitE $ OBool b
return . LitE $ OBool b
-- FIXME: this is a hack, implement something like exprN to replace this?
*<|> "number" ?: (
do
do
a <- many1 digit
_ <- char 'e'
b <- many1 digit
return . LitE $ ONum $ read a * (10 ** read b)
*<|> do
a <- many1 digit
_ <- char '.'
b <- many digit
return $ LitE $ ONum (read (a ++ "." ++ b) :: )
_ <- char 'e'
c <- many1 digit
return . LitE $ ONum $ read (a ++ "." ++ b) * (10 ** read c)
*<|> do
a <- many1 digit
return $ LitE $ ONum (read a :: )
_ <- char '.'
b <- many digit
_ <- char 'e'
_ <- char '+'
c <- many1 digit
return . LitE $ ONum $ read (a ++ "." ++ b) * (10 ** read c)
*<|> do
a <- many1 digit
_ <- char '.'
b <- many digit
_ <- char 'e'
_ <- char '-'
c <- many1 digit
return . LitE $ ONum $ read (a ++ "." ++ b) / (10 ** read c)
*<|> do
a <- many1 digit
_ <- char 'e'
_ <- char '-'
b <- many1 digit
return . LitE $ ONum $ read a / (10 ** read b)
*<|> do
a <- many1 digit
_ <- char '.'
b <- many digit
return . LitE $ ONum $ read (a ++ "." ++ b)
*<|> do
a <- many1 digit
return . LitE $ ONum $ read a
)
*<|> "string" ?: do
*<|> "string" ?: do
_ <- string "\""
strlit <- many $ (string "\\\"" >> return '\"')
*<|> (string "\\n" >> return '\n')
*<|> ( noneOf "\"\n")
*<|> (string "\\r" >> return '\r')
*<|> (string "\\t" >> return '\t')
*<|> (string "\\\\" >> return '\\')
-- FIXME: no \u unicode support?
*<|> noneOf "\"\n"
_ <- string "\""
return $ LitE $ OString strlit
return . LitE $ OString strlit
letExpr :: GenParser Char st Expr
letExpr = "let expression" ?: do
_ <- string "let"
_ <- genSpace
_ <- string "("
_ <- genSpace
bindingPairs <- sepBy ( do
_ <- genSpace
boundName <- variableSymb
_ <- genSpace
_ <- string "="
_ <- genSpace
boundExpr <- expr0
return $ ListE [Var boundName, boundExpr])
(char ',')
_ <- string ")"
expr <- expr0
let bindLets (ListE [Var boundName, boundExpr]) nestedExpr = (LamE [Name boundName] nestedExpr) :$ [boundExpr]
bindLets _ e = e
return $ foldr bindLets expr bindingPairs
-- We represent the priority or 'fixity' of different types of expressions
-- by the Int argument
-- by the ExprIdx argument, with A0 as the highest.
expr0 :: GenParser Char st Expr
expr0 = exprN A0
@ -55,6 +114,7 @@ exprN :: ExprIdx -> GenParser Char st Expr
exprN A12 =
literal
*<|> letExpr
*<|> variable
*<|> "bracketed expression" ?: do
-- eg. ( 1 + 5 )
@ -85,7 +145,7 @@ exprN A12 =
exprN A11 =
do
obj <- exprN $ A12
obj <- exprN A12
_ <- genSpace
mods <- many1 (
"function application" ?: do
@ -111,67 +171,73 @@ exprN A11 =
(Just s, Just e ) -> \l -> Var "splice" :$ [l, s, e]
)
return $ foldl (\a b -> b a) obj mods
*<|> (exprN $ A12 )
*<|> exprN A12
-- match a leading (+) or (-) operator.
exprN A10 =
"negation" ?: do
_ <- padString "-"
expr <- exprN $ A11
expr <- exprN A11
return $ Var "negate" :$ [expr]
*<|> do
_ <- padString "+"
expr <- exprN $ A11
return expr
*<|> exprN (A11)
exprN A11
*<|> exprN A11
-- match power-of (^) operator.
exprN A9 =
"exponentiation" ?: do
a <- exprN $ A10
a <- exprN A10
_ <- padString "^"
b <- exprN A9
return $ Var "^" :$ [a,b]
*<|> exprN (A10)
*<|> exprN A10
-- match sequences of multiplication and division.
exprN A8 =
"multiplication/division" ?: do
-- outer list is multiplication, inner division.
-- eg. "1*2*3/4/5*6*7/8"
-- [[1],[2],[3,4,5],[6],[7,8]]
exprs <- sepBy1
(sepBy1 (exprN $ A9) (try $ padString "/" ))
(sepBy1 (exprN A9) (try $ padString "/" ))
(try $ padString "*" )
let div' a b = Var "/" :$ [a, b]
return $ collector "*" $ map (foldl1 div') exprs
*<|> exprN (A9)
return . collector "*" $ map (foldl1 div') exprs
*<|> exprN A9
-- match remainder (%) operator.
exprN A7 =
"modulo" ?: do
exprs <- sepBy1 (exprN $ A8) (try $ padString "%")
exprs <- sepBy1 (exprN A8) (try $ padString "%")
let mod' a b = Var "%" :$ [a, b]
return $ foldl1 mod' exprs
*<|> exprN (A8)
*<|> exprN A8
-- match string addition (++) operator.
exprN A6 =
"append" ?: do
exprs <- sepBy1 (exprN $ A7) (try $ padString "++")
exprs <- sepBy1 (exprN A7) (try $ padString "++")
return $ collector "++" exprs
*<|> exprN (A7)
*<|> exprN A7
-- match sequences of addition and subtraction.
exprN A5 =
"addition/subtraction" ?: do
-- Similar to multiply & divide
-- eg. "1+2+3-4-5+6-7"
-- [[1],[2],[3,4,5],[6,7]]
exprs <- sepBy1
(sepBy1 (exprN $ A6) (try $ padString "-" ))
(sepBy1 (exprN A6) (try $ padString "-" ))
(try $ padString "+" )
let sub a b = Var "-" :$ [a, b]
return $ collector "+" $ map (foldl1 sub) exprs
*<|> exprN (A6)
return . collector "+" $ map (foldl1 sub) exprs
*<|> exprN A6
-- match comparison operators.
exprN A4 =
do
firstExpr <- exprN $ A5
firstExpr <- exprN A5
otherComparisonsExpr <- many $ do
comparisonSymb <-
padString "=="
@ -180,7 +246,7 @@ exprN A4 =
*<|> padString "<="
*<|> padString ">"
*<|> padString "<"
expr <- exprN $ A5
expr <- exprN A5
return (Var comparisonSymb, expr)
let
(comparisons, otherExprs) = unzip otherComparisonsExpr
@ -189,39 +255,43 @@ exprN A4 =
[] -> firstExpr
[x] -> x :$ exprs
_ -> collector "all" $ zipWith3 (\c e1 e2 -> c :$ [e1,e2]) comparisons exprs (tail exprs)
*<|> exprN (A5)
*<|> exprN A5
-- match the logical negation operator.
exprN A3 =
"logical-not" ?: do
_ <- padString "!"
a <- exprN $ A4
a <- exprN A4
return $ Var "!" :$ [a]
*<|> exprN (A4)
*<|> exprN A4
-- match the logical And and Or (&&,||) operators.
exprN A2 =
"logical and/or" ?: do
a <- exprN $ A3
a <- exprN A3
symb <- padString "&&"
*<|> padString "||"
b <- exprN A2
return $ Var symb :$ [a,b]
*<|> exprN (A3)
*<|> exprN A3
-- match the ternary (1?2:3) operator.
exprN A1 =
"ternary" ?: do
a <- exprN $ A2
a <- exprN A2
_ <- padString "?"
b <- exprN A1
_ <- padString ":"
c <- exprN A1
return $ Var "?" :$ [a,b,c]
*<|> exprN (A2)
*<|> exprN A2
-- Match and throw away any white space around an expression.
exprN A0 =
do
_ <- genSpace
expr <- exprN $ A1
expr <- exprN A1
_ <- genSpace
return expr
*<|> exprN (A1)
*<|> exprN A1

View File

@ -8,29 +8,36 @@
-- FIXME: required. why?
{-# LANGUAGE KindSignatures #-}
module Graphics.Implicit.ExtOpenScad.Parser.Statement where
-- The entry point for parsing an ExtOpenScad program.
module Graphics.Implicit.ExtOpenScad.Parser.Statement (parseProgram) where
import Prelude(Char, Either, String, Maybe(Just, Nothing), Monad, return, fmap, ($), (>>), Bool(False, True), map)
import Prelude(Char, Either, String, Monad, return, fmap, ($), (>>), Bool(False, True), map)
import Text.ParserCombinators.Parsec (try, sepBy, sourceLine, GenParser, oneOf, space, char, getPosition, parse, many1, eof, string, SourceName, ParseError, many, noneOf, Line, (<|>), (<?>))
import Text.Parsec.Prim (ParsecT)
import Data.Maybe(Maybe(Just, Nothing))
import Data.Functor.Identity(Identity)
import Data.Kind (Type)
-- We use parsec to parse.
import Text.ParserCombinators.Parsec (try, sepBy, sourceLine, sourceColumn, GenParser, oneOf, space, char, getPosition, parse, many1, eof, string, ParseError, many, noneOf, Line, Column, (<|>), (<?>))
import Text.Parsec.Prim (ParsecT)
import Graphics.Implicit.ExtOpenScad.Definitions (Pattern(Name), Statement(DoNothing, NewModule, Include, Echo, If, For, ModuleCall,(:=)),Expr(LamE), StatementI(StatementI))
import Graphics.Implicit.ExtOpenScad.Parser.Util (genSpace, tryMany, stringGS, (*<|>), (?:), patternMatcher, variableSymb)
-- the top level of the expression parser.
import Graphics.Implicit.ExtOpenScad.Parser.Expr (expr0)
parseProgram :: SourceName -> [Char] -> Either ParseError [StatementI]
parseProgram name s = parse program name s where
program :: ParsecT [Char] u Identity [StatementI]
parseProgram :: String -> Either ParseError [StatementI]
parseProgram = parse program "" where -- "" is our program name.
program :: ParsecT String u Identity [StatementI]
program = do
sts <- many1 computation
eof
return sts
-- | A in our programming openscad-like programming language.
-- | A computable block of code in our openscad-like programming language.
computation :: GenParser Char st StatementI
computation =
do -- suite statements: no semicolon...
@ -39,30 +46,21 @@ computation =
ifStatementI,
forStatementI,
throwAway,
userModuleDeclaration{-,
unimplemented "mirror",
unimplemented "multmatrix",
unimplemented "color",
unimplemented "render",
unimplemented "surface",
unimplemented "projection",
unimplemented "import_stl"-}
-- rotateExtrude
userModuleDeclaration
]
_ <- genSpace
return s
*<|> do -- Non suite s. Semicolon needed...
*<|> do -- Non suite statements. Semicolon needed...
_ <- genSpace
s <- tryMany [
echo,
include,
include, -- also handles use
function,
assignment--,
--use
assignment
]
_ <- stringGS " ; "
return s
*<|> do -- Modules
*<|> do -- Modules. no semicolon...
_ <- genSpace
s <- userModule
_ <- genSpace
@ -81,9 +79,8 @@ computation =
--
-- union() sphere(3);
--
-- We consider it to be a list of s which
-- We consider it to be a list of computables which
-- are in turn StatementI s.
-- So this parses them.
-}
suite :: GenParser Char st [StatementI]
suite = (fmap return computation <|> do
@ -95,110 +92,120 @@ suite = (fmap return computation <|> do
return stmts
) <?> " suite"
-- | commenting out a comuptation: use % or * before the statement, and it will not be run.
throwAway :: GenParser Char st StatementI
throwAway = do
line <- lineNumber
column <- columnNumber
_ <- genSpace
_ <- oneOf "%*"
_ <- genSpace
_ <- computation
return $ StatementI line DoNothing
return $ StatementI line column DoNothing
-- An included ! Basically, inject another openscad file here...
-- | An include! Basically, inject another extopenscad file here...
include :: GenParser Char st StatementI
include = (do
line <- lineNumber
column <- columnNumber
injectVals <- (string "include" >> return True )
<|> (string "use" >> return False)
_ <- stringGS " < "
filename <- many (noneOf "<> ")
_ <- stringGS " > "
return $ StatementI line $ Include filename injectVals
return $ StatementI line column $ Include filename injectVals
) <?> "include "
-- | An assignment (parser)
-- | An assignment (parser)
assignment :: GenParser Char st StatementI
assignment = ("assignment " ?:) $
do
line <- lineNumber
pattern <- patternMatcher
column <- columnNumber
lvalue <- patternMatcher
_ <- stringGS " = "
valExpr <- expr0
return $ StatementI line$ pattern := valExpr
return $ StatementI line column $ lvalue := valExpr
-- | A function declaration (parser)
function :: GenParser Char st StatementI
function = ("function " ?:) $
do
line <- lineNumber
varSymb <- (string "function" >> space >> genSpace >> variableSymb)
column <- columnNumber
varSymb <- string "function" >> space >> genSpace >> variableSymb
_ <- stringGS " ( "
argVars <- sepBy patternMatcher (stringGS " , ")
_ <- stringGS " ) = "
valExpr <- expr0
return $ StatementI line $ Name varSymb := LamE argVars valExpr
return $ StatementI line column $ Name varSymb := LamE argVars valExpr
-- | An echo (parser)
-- | An echo (parser)
echo :: GenParser Char st StatementI
echo = do
line <- lineNumber
column <- columnNumber
_ <- stringGS "echo ( "
exprs <- expr0 `sepBy` (stringGS " , ")
exprs <- expr0 `sepBy` stringGS " , "
_ <- stringGS " ) "
return $ StatementI line $ Echo exprs
return $ StatementI line column $ Echo exprs
ifStatementI :: GenParser Char st StatementI
ifStatementI =
"if " ?: do
line <- lineNumber
column <- columnNumber
_ <- stringGS "if ( "
bexpr <- expr0
_ <- stringGS " ) "
sTrueCase <- suite
_ <- genSpace
sFalseCase <- (stringGS "else " >> suite ) *<|> (return [])
return $ StatementI line $ If bexpr sTrueCase sFalseCase
sFalseCase <- (stringGS "else " >> suite ) *<|> return []
return $ StatementI line column $ If bexpr sTrueCase sFalseCase
forStatementI :: GenParser Char st StatementI
forStatementI =
"for " ?: do
line <- lineNumber
column <- columnNumber
-- a for loop is of the form:
-- for ( vsymb = vexpr ) loops
-- eg. for ( a = [1,2,3] ) {echo(a); echo "lol";}
-- eg. for ( [a,b] = [[1,2]] ) {echo(a+b); echo "lol";}
_ <- stringGS "for ( "
pattern <- patternMatcher
lvalue <- patternMatcher
_ <- stringGS " = "
vexpr <- expr0
_ <- stringGS " ) "
loopContent <- suite
return $ StatementI line $ For pattern vexpr loopContent
return $ StatementI line column $ For lvalue vexpr loopContent
-- | parse a call to a module.
userModule :: GenParser Char st StatementI
userModule = do
line <- lineNumber
column <- columnNumber
name <- variableSymb
_ <- genSpace
args <- moduleArgsUnit
_ <- genSpace
s <- suite *<|> (stringGS " ; " >> return [])
return $ StatementI line $ ModuleCall name args s
return $ StatementI line column $ ModuleCall name args s
-- | declare a module.
userModuleDeclaration :: GenParser Char st StatementI
userModuleDeclaration = do
line <- lineNumber
column <- columnNumber
_ <- stringGS "module "
newModuleName <- variableSymb
_ <- genSpace
args <- moduleArgsUnitDecl
_ <- genSpace
s <- suite
return $ StatementI line $ NewModule newModuleName args s
----------------------
return $ StatementI line column $ NewModule newModuleName args s
-- | parse the arguments passed to a module.
moduleArgsUnit :: GenParser Char st [(Maybe String, Expr)]
moduleArgsUnit = do
_ <- stringGS " ( "
@ -208,7 +215,7 @@ moduleArgsUnit = do
symb <- variableSymb
_ <- stringGS " = "
expr <- expr0
return $ (Just symb, expr)
return (Just symb, expr)
*<|> do
-- eg. a(x,y) = 12
symb <- variableSymb
@ -216,7 +223,7 @@ moduleArgsUnit = do
argVars <- sepBy variableSymb (try $ stringGS " , ")
_ <- stringGS " ) = "
expr <- expr0
return $ (Just symb, LamE (map Name argVars) expr)
return (Just symb, LamE (map Name argVars) expr)
*<|> do
-- eg. 12
expr <- expr0
@ -225,6 +232,7 @@ moduleArgsUnit = do
_ <- stringGS " ) "
return args
-- | parse the arguments in the module declaration.
moduleArgsUnitDecl :: GenParser Char st [(String, Maybe Expr)]
moduleArgsUnitDecl = do
_ <- stringGS " ( "
@ -241,8 +249,6 @@ moduleArgsUnitDecl = do
_ <- sepBy variableSymb (try $ stringGS " , ")
_ <- stringGS " ) = "
expr <- expr0
-- FIXME: this line looks right, but.. what does this change?
-- return $ (Just symb, LamE (map Name argVars) expr)
return (symb, Just expr)
*<|> do
symb <- variableSymb
@ -251,13 +257,13 @@ moduleArgsUnitDecl = do
_ <- stringGS " ) "
return argTemplate
lineNumber :: forall s u (m :: * -> *).
-- | Find the line number. Used when generating errors.
lineNumber :: forall s u (m :: Type -> Type).
Monad m => ParsecT s u m Line
lineNumber = fmap sourceLine getPosition
--FIXME: use the below function to improve error reporting.
{-
columnNumber :: forall s u (m :: * -> *).
-- | Find the column number. Used when generating errors.
columnNumber :: forall s u (m :: Type -> Type).
Monad m => ParsecT s u m Column
columnNumber = fmap sourceColumn getPosition
-}

View File

@ -13,26 +13,31 @@ module Graphics.Implicit.ExtOpenScad.Parser.Util (genSpace, pad, (*<|>), (?:), s
import Prelude (String, Char, ($), (++), foldl1, map, (>>), (.), return)
import Text.ParserCombinators.Parsec (GenParser, many, oneOf, noneOf, (<|>), try, string, manyTill, anyChar, (<?>), char, many1, sepBy)
import Text.Parsec.Prim (ParsecT, Stream)
import Data.Functor.Identity (Identity)
import Graphics.Implicit.ExtOpenScad.Definitions (Pattern(Wild, Name, ListP))
import Data.Kind (Type)
-- white space, including tabs, newlines and comments
genSpace :: ParsecT [Char] u Identity [Char]
genSpace :: ParsecT String u Identity String
genSpace = many $
oneOf " \t\n\r"
<|> (try $ do
<|> try ( do
_ <- string "//"
_ <- many ( noneOf "\n")
_ <- string "\n"
return ' '
) <|> (try $ do
) <|> try ( do
_ <- string "/*"
_ <- manyTill anyChar (try $ string "*/")
return ' '
)
pad :: forall b u. ParsecT [Char] u Identity b -> ParsecT [Char] u Identity b
-- a padded ... parser?
pad :: ParsecT String u Identity b -> ParsecT String u Identity b
pad parser = do
_ <- genSpace
a <- parser
@ -44,10 +49,10 @@ infixr 1 *<|>
a *<|> b = try a <|> b
infixr 2 ?:
(?:) :: forall s u (m :: * -> *) a. String -> ParsecT s u m a -> ParsecT s u m a
(?:) :: forall s u (m :: Type -> Type) a. String -> ParsecT s u m a -> ParsecT s u m a
l ?: p = p <?> l
stringGS :: [Char] -> ParsecT [Char] u Identity [Char]
stringGS :: String -> ParsecT String u Identity String
stringGS (' ':xs) = do
x' <- genSpace
xs' <- stringGS xs
@ -58,7 +63,8 @@ stringGS (x:xs) = do
return (x' : xs')
stringGS "" = return ""
padString :: String -> ParsecT [Char] u Identity String
-- a padded string
padString :: String -> ParsecT String u Identity String
padString s = do
_ <- genSpace
s' <- string s
@ -66,10 +72,11 @@ padString s = do
return s'
tryMany :: forall u a tok. [GenParser tok u a] -> ParsecT [tok] u Identity a
tryMany = (foldl1 (<|>)) . (map try)
tryMany = foldl1 (<|>) . map try
variableSymb :: forall s u (m :: * -> *). Stream s m Char => ParsecT s u m [Char]
variableSymb :: forall s u (m :: Type -> Type). Stream s m Char => ParsecT s u m String
variableSymb = many1 (noneOf " ,|[]{}()+-*&^%#@!~`'\"\\/;:.,<>?=") <?> "variable"
{-# INLINABLE variableSymb #-}
patternMatcher :: GenParser Char st Pattern
patternMatcher =
@ -88,7 +95,7 @@ patternMatcher =
) <|> ( do
_ <- char '['
_ <- genSpace
components <- patternMatcher `sepBy` (try $ genSpace >> char ',' >> genSpace)
components <- patternMatcher `sepBy` try (genSpace >> char ',' >> genSpace)
_ <- genSpace
_ <- char ']'
return $ ListP components

View File

@ -2,19 +2,22 @@
-- Copyright 2016, Julia Longtin (julial@turinglace.com)
-- Released under the GNU AGPLV3+, see LICENSE
-- Idealy, we'd like to parse openscad code, with some improvements, for backwards compatability.
-- Idealy, we'd like to parse a superset of openscad code, with some improvements.
-- This file provides primitive objects for the openscad parser.
-- FIXME: why are these required?
{-# LANGUAGE FlexibleContexts, ScopedTypeVariables #-}
-- For the type arithmatic involved in calling VectorSpace.
{-# LANGUAGE TypeFamilies #-}
-- Export one set containing all of the primitive object's patern matches.
module Graphics.Implicit.ExtOpenScad.Primitives (primitives) where
import Prelude(String, IO, Char, Either(Left, Right), Bool(False), Maybe(Just, Nothing), Fractional, ($), return, either, id, (-), (==), (&&), (<), fromIntegral, (*), cos, sin, pi, (/), (>), const, uncurry, realToFrac, fmap, fromInteger, round, (/=), (||), not, null, map, (++), putStrLn)
import Prelude(String, IO, Either(Left, Right), Bool(False), Maybe(Just, Nothing), ($), return, either, id, (-), (==), (&&), (<), (*), cos, sin, pi, (/), (>), const, uncurry, fmap, fromInteger, round, (/=), (||), not, null, map, (++), putStrLn)
import Graphics.Implicit.Definitions (, 2, 3, , SymbolicObj2, SymbolicObj3)
import Graphics.Implicit.Definitions (, 2, 3, , SymbolicObj2, SymbolicObj3, fromto)
import Graphics.Implicit.ExtOpenScad.Definitions (OVal (OObj2, OObj3), ArgParser)
@ -22,22 +25,23 @@ import Graphics.Implicit.ExtOpenScad.Util.ArgParser (doc, defaultTo, argument, e
import Graphics.Implicit.ExtOpenScad.Util.OVal (caseOType, divideObjs, (<||>))
-- Note the use of a qualified import, so we don't have the functions in this file conflict with what we're importing.
import qualified Graphics.Implicit.Primitives as Prim (sphere, rect3R, rectR, translate, circle, polygonR, extrudeR, cylinder2, union, unionR, intersect, intersectR, difference, differenceR, rotate, rotate3V, rotate3, scale, extrudeR, extrudeRM, rotateExtrude, shell, pack3, pack2)
import Data.Maybe (isNothing)
import qualified Control.Monad as Monad
import Control.Monad (mplus)
import Data.VectorSpace (VectorSpace, Scalar, (*^))
import GHC.Real (RealFrac)
primitives :: [(String, [OVal] -> ArgParser (IO [OVal]) )]
-- | The only thing exported here. basically, a list of functions, which accept OVal arguments and retrun an ArgParser ?
primitives :: [(String, [OVal] -> ArgParser (IO [OVal]))]
primitives = [ sphere, cube, square, cylinder, circle, polygon, union, difference, intersect, translate, scale, rotate, extrude, pack, shell, rotateExtrude, unit ]
-- sphere is a module without a suite.
-- this means that the parser will look for this like
-- sphere(args...);
sphere :: ([Char], [OVal] -> ArgParser (IO [OVal]))
-- | sphere is a module without a suite.
-- this means that the parser will look for this like
-- sphere(args...);
sphere :: (String, [OVal] -> ArgParser (IO [OVal]))
sphere = moduleWithoutSuite "sphere" $ do
example "sphere(3);"
example "sphere(r=5);"
@ -52,14 +56,16 @@ sphere = moduleWithoutSuite "sphere" $ do
-- (Graphics.Implicit.Primitives)
addObj3 $ Prim.sphere r
cube :: ([Char], [OVal] -> ArgParser (IO [OVal]))
cube :: (String, [OVal] -> ArgParser (IO [OVal]))
cube = moduleWithoutSuite "cube" $ do
-- examples
example "cube(size = [2,3,4], center = true, r = 0.5);"
example "cube(4);"
-- arguments
-- arguments shared between forms
r :: <- argument "r"
`doc` "radius of rounding"
`defaultTo` 0
-- arguments (two forms)
((x1,x2), (y1,y2), (z1,z2)) <-
do
x :: Either 2 <- argument "x"
@ -72,7 +78,7 @@ cube = moduleWithoutSuite "cube" $ do
`doc` "should center? (non-intervals)"
`defaultTo` False
let
toInterval' :: Fractional t => t -> (t, t)
toInterval' :: -> (, )
toInterval' = toInterval center
return (either toInterval' id x,
either toInterval' id y,
@ -85,28 +91,24 @@ cube = moduleWithoutSuite "cube" $ do
`defaultTo` False
let (x,y, z) = either (\w -> (w,w,w)) id size
return (toInterval center x, toInterval center y, toInterval center z)
r :: <- argument "r"
`doc` "radius of rounding"
`defaultTo` 0
-- Tests
test "cube(4);"
`eulerCharacteristic` 2
test "cube(size=[2,3,4]);"
`eulerCharacteristic` 2
addObj3 $ Prim.rect3R r (x1, y1, z1) (x2, y2, z2)
square :: ([Char], [OVal] -> ArgParser (IO [OVal]))
square :: (String, [OVal] -> ArgParser (IO [OVal]))
square = moduleWithoutSuite "square" $ do
-- examples
example "square(x=[-2,2], y=[-1,5]);"
example "square(size = [3,4], center = true, r = 0.5);"
example "square(4);"
-- arguments
-- arguments shared between forms
r :: <- argument "r"
`doc` "radius of rounding"
`defaultTo` 0
-- arguments (two forms)
((x1,x2), (y1,y2)) <-
do
x :: Either 2 <- argument "x"
@ -117,7 +119,7 @@ square = moduleWithoutSuite "square" $ do
`doc` "should center? (non-intervals)"
`defaultTo` False
let
toInterval' :: Fractional t => t -> (t, t)
toInterval' :: -> (, )
toInterval' = toInterval center
return (either toInterval' id x,
either toInterval' id y)
@ -129,26 +131,18 @@ square = moduleWithoutSuite "square" $ do
`defaultTo` False
let (x,y) = either (\w -> (w,w)) id size
return (toInterval center x, toInterval center y)
r :: <- argument "r"
`doc` "radius of rounding"
`defaultTo` 0
-- Tests
test "square(2);"
`eulerCharacteristic` 0
test "square(size=[2,3]);"
`eulerCharacteristic` 0
addObj2 $ Prim.rectR r (x1, y1) (x2, y2)
cylinder :: ([Char], [OVal] -> ArgParser (IO [OVal]))
cylinder :: (String, [OVal] -> ArgParser (IO [OVal]))
cylinder = moduleWithoutSuite "cylinder" $ do
example "cylinder(r=10, h=30, center=true);"
example "cylinder(r1=4, r2=6, h=10);"
example "cylinder(r=5, h=10, $fn = 6);"
-- arguments
r :: <- argument "r"
`defaultTo` 1
@ -162,19 +156,17 @@ cylinder = moduleWithoutSuite "cylinder" $ do
r2 :: <- argument "r2"
`defaultTo` 1
`doc` "top radius; overrides r"
fn :: <- argument "$fn"
sides :: <- argument "$fn"
`defaultTo` (-1)
`doc` "number of sides, for making prisms"
center :: Bool <- argument "center"
`defaultTo` False
`doc` "center cylinder with respect to z?"
-- Tests
test "cylinder(r=10, h=30, center=true);"
`eulerCharacteristic` 0
test "cylinder(r=5, h=10, $fn = 6);"
`eulerCharacteristic` 0
let
(h1, h2) = either (toInterval center) id h
dh = h2 - h1
@ -183,52 +175,39 @@ cylinder = moduleWithoutSuite "cylinder" $ do
if h1 == 0
then id
else Prim.translate (0,0,h1)
-- The result is a computation state modifier that adds a 3D object,
-- based on the args.
addObj3 $ if r1 == 1 && r2 == 1
then let
obj2 = if fn < 0 then Prim.circle r else Prim.polygonR 0 $
let
sides ::
sides = fromIntegral fn
in [(r*cos θ, r*sin θ )| θ <- [2*pi*n/sides | n <- [0.0 .. sides - 1.0]]]
obj2 = if sides < 0 then Prim.circle r else Prim.polygonR 0 $
[(r*cos θ, r*sin θ )| θ <- [2*pi*(fromto n)/(fromto sides) | n <- [0 .. sides - 1]]]
obj3 = Prim.extrudeR 0 obj2 dh
in shift obj3
else shift $ Prim.cylinder2 r1 r2 dh
circle :: ([Char], [OVal] -> ArgParser (IO [OVal]))
circle :: (String, [OVal] -> ArgParser (IO [OVal]))
circle = moduleWithoutSuite "circle" $ do
example "circle(r=10); // circle"
example "circle(r=5, $fn=6); //hexagon"
-- Arguments
r :: <- argument "r"
`doc` "radius of the circle"
fn :: <- argument "$fn"
`doc` "if defined, makes a regular polygon with n sides instead of a circle"
`defaultTo` (-1)
r :: <- argument "r"
`doc` "radius of the circle"
sides :: <- argument "$fn"
`doc` "if defined, makes a regular polygon with n sides instead of a circle"
`defaultTo` (-1)
test "circle(r=10);"
`eulerCharacteristic` 0
addObj2 $ if fn < 3
addObj2 $ if sides < 3
then Prim.circle r
else Prim.polygonR 0 $
let
sides ::
sides = fromIntegral fn
in [(r*cos θ, r*sin θ )| θ <- [2*pi*n/sides | n <- [0.0 .. sides - 1.0]]]
[(r*cos θ, r*sin θ )| θ <- [2*pi*(fromto n)/(fromto sides) | n <- [0 .. sides - 1]]]
polygon :: ([Char], [OVal] -> ArgParser (IO [OVal]))
polygon :: (String, [OVal] -> ArgParser (IO [OVal]))
polygon = moduleWithoutSuite "polygon" $ do
example "polygon ([(0,0), (0,10), (10,0)]);"
points :: [2] <- argument "points"
points :: [2] <- argument "points"
`doc` "vertices of the polygon"
paths :: [ ] <- argument "paths"
paths :: [] <- argument "paths"
`doc` "order to go through vertices; ignored for now"
`defaultTo` []
r :: <- argument "r"
@ -238,8 +217,7 @@ polygon = moduleWithoutSuite "polygon" $ do
[] -> addObj2 $ Prim.polygonR r points
_ -> return $ return []
union :: ([Char], [OVal] -> ArgParser (IO [OVal]))
union :: (String, [OVal] -> ArgParser (IO [OVal]))
union = moduleWithSuite "union" $ \children -> do
r :: <- argument "r"
`defaultTo` 0.0
@ -248,7 +226,7 @@ union = moduleWithSuite "union" $ \children -> do
then objReduce (Prim.unionR r) (Prim.unionR r) children
else objReduce Prim.union Prim.union children
intersect :: ([Char], [OVal] -> ArgParser (IO [OVal]))
intersect :: (String, [OVal] -> ArgParser (IO [OVal]))
intersect = moduleWithSuite "intersection" $ \children -> do
r :: <- argument "r"
`defaultTo` 0.0
@ -257,7 +235,7 @@ intersect = moduleWithSuite "intersection" $ \children -> do
then objReduce (Prim.intersectR r) (Prim.intersectR r) children
else objReduce Prim.intersect Prim.intersect children
difference :: ([Char], [OVal] -> ArgParser (IO [OVal]))
difference :: (String, [OVal] -> ArgParser (IO [OVal]))
difference = moduleWithSuite "difference" $ \children -> do
r :: <- argument "r"
`defaultTo` 0.0
@ -266,12 +244,10 @@ difference = moduleWithSuite "difference" $ \children -> do
then objReduce (Prim.differenceR r) (Prim.differenceR r) children
else objReduce Prim.difference Prim.difference children
translate :: ([Char], [OVal] -> ArgParser (IO [OVal]))
translate :: (String, [OVal] -> ArgParser (IO [OVal]))
translate = moduleWithSuite "translate" $ \children -> do
example "translate ([2,3]) circle (4);"
example "translate ([5,6,7]) sphere(5);"
(x,y,z) <-
do
x :: <- argument "x"
@ -289,7 +265,6 @@ translate = moduleWithSuite "translate" $ \children -> do
Left x -> (x,0,0)
Right (Left (x,y) ) -> (x,y,0)
Right (Right (x,y,z)) -> (x,y,z)
return $ return $
objMap (Prim.translate (x,y)) (Prim.translate (x,y,z)) children
@ -297,13 +272,13 @@ deg2rad :: ->
deg2rad x = x / 180.0 * pi
-- This is mostly insane
rotate :: ([Char], [OVal] -> ArgParser (IO [OVal]))
rotate :: (String, [OVal] -> ArgParser (IO [OVal]))
rotate = moduleWithSuite "rotate" $ \children -> do
a <- argument "a"
`doc` "value to rotate by; angle or list of angles"
v <- argument "v" `defaultTo` (0, 0, 1)
v <- argument "v"
`defaultTo` (0, 0, 1)
`doc` "Vector to rotate around if a is a single angle"
-- caseOType matches depending on whether size can be coerced into
-- the right object. See Graphics.Implicit.ExtOpenScad.Util
-- Entries must be joined with the operator <||>
@ -317,30 +292,25 @@ rotate = moduleWithSuite "rotate" $ \children -> do
objMap id (Prim.rotate3 (deg2rad yz, deg2rad zx, 0)) children
) <||> const []
scale :: ([Char], [OVal] -> ArgParser (IO [OVal]))
scale :: (String, [OVal] -> ArgParser (IO [OVal]))
scale = moduleWithSuite "scale" $ \children -> do
example "scale(2) square(5);"
example "scale([2,3]) square(5);"
example "scale([2,3,4]) cube(5);"
v :: Either (Either 2 3) <- argument "v"
v <- argument "v"
`doc` "vector or scalar to scale by"
let
scaleObjs stretch2 stretch3 =
objMap (Prim.scale stretch2) (Prim.scale stretch3) children
return $ return $ case v of
Left x -> scaleObjs (x,1) (x,1,1)
Right (Left (x,y)) -> scaleObjs (x,y) (x,y,1)
Right (Right (x,y,z)) -> scaleObjs (x,y) (x,y,z)
extrude :: ([Char], [OVal] -> ArgParser (IO [OVal]))
extrude :: (String, [OVal] -> ArgParser (IO [OVal]))
extrude = moduleWithSuite "linear_extrude" $ \children -> do
example "linear_extrude(10) square(5);"
height :: Either ( -> -> ) <- argument "height" `defaultTo` (Left 1)
height :: Either ( -> -> ) <- argument "height" `defaultTo` Left 1
`doc` "height to extrude to..."
center :: Bool <- argument "center" `defaultTo` False
`doc` "center? (the z component)"
@ -352,7 +322,6 @@ extrude = moduleWithSuite "linear_extrude" $ \children -> do
`doc` "translate according to this funciton as we extrude..."
r :: <- argument "r" `defaultTo` 0
`doc` "round the top?"
let
heightn = case height of
Left h -> h
@ -361,21 +330,17 @@ extrude = moduleWithSuite "linear_extrude" $ \children -> do
height' = case height of
Right f -> Right $ uncurry f
Left a -> Left a
shiftAsNeeded :: SymbolicObj3 -> SymbolicObj3
shiftAsNeeded =
if center
then Prim.translate (0,0,-heightn/2.0)
else id
funcify :: (VectorSpace a, Fractional (Scalar a)) => Either a ( -> a) -> -> a
funcify (Left val) h = realToFrac (h/heightn) *^ val
funcify :: (VectorSpace a, s ~ (Scalar a), s ~ ) => Either a ( -> a) -> -> a
funcify (Left val) h = (h/heightn) *^ val
funcify (Right f ) h = f h
twist' = fmap funcify twist
scale' = fmap funcify scaleArg
translate' = fmap funcify translateArg
return $ return $ obj2UpMap (
\obj -> case height of
Left constHeight | isNothing twist && isNothing scaleArg && isNothing translateArg ->
@ -384,55 +349,50 @@ extrude = moduleWithSuite "linear_extrude" $ \children -> do
shiftAsNeeded $ Prim.extrudeRM r twist' scale' translate' obj height'
) children
rotateExtrude :: ([Char], [OVal] -> ArgParser (IO [OVal]))
rotateExtrude :: (String, [OVal] -> ArgParser (IO [OVal]))
rotateExtrude = moduleWithSuite "rotate_extrude" $ \children -> do
example "rotate_extrude() translate(20) circle(10);"
totalRot :: <- argument "a" `defaultTo` 360
`doc` "angle to sweep"
totalRot :: <- argument "a" `defaultTo` 360
`doc` "angle to sweep"
r :: <- argument "r" `defaultTo` 0
translateArg :: Either 2 ( -> 2) <- argument "translate" `defaultTo` Left (0,0)
rotateArg :: Either ( -> ) <- argument "rotate" `defaultTo` Left 0
let
is360m :: RealFrac a => a -> Bool
is360m :: -> Bool
is360m n = 360 * fromInteger (round $ n / 360) /= n
cap = is360m totalRot
|| (either ( /= (0,0)) (\f -> f 0 /= f totalRot) ) translateArg
|| (either (is360m) (\f -> is360m (f 0 - f totalRot)) ) rotateArg
|| either ( /= (0,0)) (\f -> f 0 /= f totalRot) translateArg
|| either is360m (\f -> is360m (f 0 - f totalRot)) rotateArg
capM = if cap then Just r else Nothing
return $ return $ obj2UpMap (Prim.rotateExtrude totalRot capM translateArg rotateArg) children
{-
rotateExtrudeStatement :: (String, [OVal] -> ArgParser (IO [OVal]))
rotateExtrudeStatement = moduleWithSuite "rotate_extrude" $ \suite -> do
-- arguments
h :: <- realArgument "h"
center :: Bool <- boolArgumentWithDefault "center" False
twist :: <- realArgumentWithDefault 0.0
r :: <- realArgumentWithDefault "r" 0.0
{-rotateExtrudeStatement = moduleWithSuite "rotate_extrude" $ \suite -> do
h <- realArgument "h"
center <- boolArgumentWithDefault "center" False
twist <- realArgumentWithDefault 0.0
r <- realArgumentWithDefault "r" 0.0
getAndModUpObj2s suite (\obj -> extrudeRMod r (\θ (x,y) -> (x*cos(θ)+y*sin(θ), y*cos(θ)-x*sin(θ)) ) obj h)
-}
shell :: ([Char], [OVal] -> ArgParser (IO [OVal]))
shell = moduleWithSuite "shell" $ \children-> do
shell :: (String, [OVal] -> ArgParser (IO [OVal]))
shell = moduleWithSuite "shell" $ \children -> do
w :: <- argument "w"
`doc` "width of the shell..."
return $ return $ objMap (Prim.shell w) (Prim.shell w) children
-- Not a perenant solution! Breaks if can't pack.
pack :: ([Char], [OVal] -> ArgParser (IO [OVal]))
-- Not a permanent solution! Breaks if can't pack.
pack :: (String, [OVal] -> ArgParser (IO [OVal]))
pack = moduleWithSuite "pack" $ \children -> do
example "pack ([45,45], sep=2) { circle(10); circle(10); circle(10); circle(10); }"
-- arguments
size :: 2 <- argument "size"
`doc` "size of 2D box to pack objects within"
sep :: <- argument "sep"
`doc` "mandetory space between objects"
-- The actual work...
return $
let (obj2s, obj3s, others) = divideObjs children
@ -448,17 +408,14 @@ pack = moduleWithSuite "pack" $ \children -> do
putStrLn "Can't pack given objects in given box with present algorithm"
return children
unit :: ([Char], [OVal] -> ArgParser (IO [OVal]))
unit :: (String, [OVal] -> ArgParser (IO [OVal]))
unit = moduleWithSuite "unit" $ \children -> do
example "unit(\"inch\") {..}"
-- arguments
unitName :: String <- argument "unit"
name :: String <- argument "unit"
`doc` "the unit you wish to work in"
let
mmRatio :: Fractional a => [Char] -> Maybe a
mmRatio :: String -> Maybe
mmRatio "inch" = Just 25.4
mmRatio "in" = mmRatio "inch"
mmRatio "foot" = Just 304.8
@ -474,32 +431,30 @@ unit = moduleWithSuite "unit" $ \children -> do
mmRatio "um" = mmRatio "µm"
mmRatio "nm" = Just 0.0000001
mmRatio _ = Nothing
-- The actual work...
return $ case mmRatio unitName of
return $ case mmRatio name of
Nothing -> do
putStrLn $ "unrecognized unit " ++ unitName
putStrLn $ "unrecognized unit " ++ name
return children
Just r ->
return $ objMap (Prim.scale (r,r)) (Prim.scale (r,r,r)) children
---------------
(<|>) :: ArgParser a -> ArgParser a -> ArgParser a
(<|>) = Monad.mplus
(<|>) = mplus
moduleWithSuite :: t -> t1 -> (t, t1)
moduleWithSuite name modArgMapper = (name, modArgMapper)
moduleWithoutSuite :: t -> a -> (t, b -> a)
moduleWithoutSuite name modArgMapper = (name, const modArgMapper)
addObj3 :: SymbolicObj3 -> ArgParser (IO [OVal])
addObj3 x = return $ return [OObj3 x]
addObj2 :: SymbolicObj2 -> ArgParser (IO [OVal])
addObj2 x = return $ return [OObj2 x]
addObj3 :: SymbolicObj3 -> ArgParser (IO [OVal])
addObj3 x = return $ return [OObj3 x]
objMap :: (SymbolicObj2 -> SymbolicObj2) -> (SymbolicObj3 -> SymbolicObj3) -> [OVal] -> [OVal]
objMap obj2mod obj3mod (x:xs) = case x of
OObj2 obj2 -> OObj2 (obj2mod obj2) : objMap obj2mod obj3mod xs
@ -520,7 +475,7 @@ obj2UpMap obj2upmod (x:xs) = case x of
a -> a : obj2UpMap obj2upmod xs
obj2UpMap _ [] = []
toInterval :: Fractional t => Bool -> t -> (t, t)
toInterval :: Bool -> -> 2
toInterval center h =
if center
then (-h/2, h/2)

View File

@ -8,20 +8,31 @@
-- FIXME: why is this required?
{-# LANGUAGE ScopedTypeVariables #-}
module Graphics.Implicit.ExtOpenScad.Util.ArgParser where
module Graphics.Implicit.ExtOpenScad.Util.ArgParser (argument, doc, defaultTo, example, test, eulerCharacteristic, argMap) where
import Prelude(String, Maybe(Just, Nothing), Int, ($), (++), concat, show, error, return, map, snd, filter, (.), fst, foldl1, not, null, (&&))
-- imported twice, once qualified. null from Data.Map conflicts with null from Prelude.
import Prelude(String, Maybe(Just, Nothing), ($), (++), concat, show, error, return, map, snd, filter, (.), fst, foldl1, not, (&&))
import qualified Prelude as Prelude (null)
import Graphics.Implicit.ExtOpenScad.Definitions (ArgParser(AP, APTest, APBranch, APTerminator, APFailIf, APExample), OVal (OError), TestInvariant(EulerCharacteristic))
import Graphics.Implicit.ExtOpenScad.Util.OVal (fromOObj, toOObj, OTypeMirror)
import qualified Data.Map as Map
import Graphics.Implicit.Definitions()
-- imported twice, once qualified. null from Data.Map conflicts with null from Prelude.
import Data.Map (fromList, Map, lookup, delete)
import qualified Data.Map as Map (null)
import Data.Maybe (isNothing, fromJust, isJust)
import Control.Arrow(first)
-- * ArgParser building functions
-- ** argument and combinators
-- | Builds an argparser for the type that is expected from it.
argument :: forall desiredType. (OTypeMirror desiredType) => String -> ArgParser desiredType
argument name =
AP name Nothing "" $ \oObjVal -> do
@ -34,6 +45,7 @@ argument name =
_ -> "arg " ++ show oObjVal ++ " not compatible with " ++ name
-- Using /= Nothing would require Eq desiredType
APFailIf (isNothing val) errmsg $ APTerminator $ fromJust val
{-# INLINABLE argument #-}
doc :: forall a. ArgParser a -> String -> ArgParser a
doc (AP name defMaybeVal _ next) newDoc = AP name defMaybeVal newDoc next
@ -54,9 +66,9 @@ example str = APExample str (return ())
test :: String -> ArgParser ()
test str = APTest str [] (return ())
eulerCharacteristic :: ArgParser a -> Int -> ArgParser a
eulerCharacteristic :: ArgParser a -> -> ArgParser a
eulerCharacteristic (APTest str tests child) χ =
APTest str ((EulerCharacteristic χ) : tests) child
APTest str (EulerCharacteristic χ : tests) child
eulerCharacteristic _ _ = error "Impossible!"
-- * Tools for handeling ArgParsers
@ -64,16 +76,16 @@ eulerCharacteristic _ _ = error "Impossible!"
-- | Apply arguments to an ArgParser
argMap ::
[(Maybe String, OVal)] -- ^ arguments
[(Maybe String, OVal)] -- ^ arguments
-> ArgParser a -- ^ ArgParser to apply them to
-> (Maybe a, [String]) -- ^ (result, error messages)
argMap args = argMap2 unnamedArgs (Map.fromList namedArgs) where
argMap args = argMap2 unnamedArgs (fromList namedArgs) where
unnamedArgs = map snd $ filter (isNothing . fst) args
namedArgs = map (\(a,b) -> (fromJust a, b)) $ filter (isJust . fst) args
namedArgs = map (first fromJust) $ filter (isJust . fst) args
argMap2 :: [OVal] -> Map.Map String OVal -> ArgParser a -> (Maybe a, [String])
argMap2 :: [OVal] -> Map String OVal -> ArgParser a -> (Maybe a, [String])
argMap2 uArgs nArgs (APBranch branches) =
foldl1 merge solutions where
@ -85,10 +97,10 @@ argMap2 uArgs nArgs (APBranch branches) =
merge (Nothing, _) a = a
argMap2 unnamedArgs namedArgs (AP name fallback _ f) =
case Map.lookup name namedArgs of
case lookup name namedArgs of
Just a -> argMap2
unnamedArgs
(Map.delete name namedArgs)
(delete name namedArgs)
(f a)
Nothing -> case unnamedArgs of
x:xs -> argMap2 xs namedArgs (f x)
@ -97,11 +109,7 @@ argMap2 unnamedArgs namedArgs (AP name fallback _ f) =
Nothing -> (Nothing, ["No value and no default for argument " ++ name])
argMap2 a b (APTerminator val) =
(Just val,
if not (null a && Map.null b)
then ["unused arguments"]
else []
)
(Just val, ["unused arguments" | not (Prelude.null a && Map.null b)])
argMap2 a b (APFailIf testval err child) =
if testval

View File

@ -3,112 +3,119 @@
-- Released under the GNU AGPLV3+, see LICENSE
-- FIXME: required. why?
{-# LANGUAGE ViewPatterns, RankNTypes, ScopedTypeVariables, TypeSynonymInstances, FlexibleInstances #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE CPP #-}
#if __GLASGOW_HASKELL__ < 710
{-# LANGUAGE OverlappingInstances #-}
#endif
module Graphics.Implicit.ExtOpenScad.Util.OVal(OTypeMirror, (<||>), fromOObj, toOObj, divideObjs, caseOType, oTypeStr, getErrors) where
module Graphics.Implicit.ExtOpenScad.Util.OVal where
import Prelude(Maybe(Just, Nothing), Bool(True, False), Either(Left,Right), Char, String, (==), fromInteger, floor, ($), (.), map, error, (++), show, head, flip, filter, not, return, head)
import Prelude(Maybe(Just, Nothing), Bool(True, False), Either(Left,Right), String, Char, (==), fromInteger, floor, ($), (.), map, error, (++), show, fromIntegral, head, flip, filter, not, return)
import Graphics.Implicit.Definitions(, , SymbolicObj2, SymbolicObj3, fromto)
import Graphics.Implicit.Definitions(, , SymbolicObj2, SymbolicObj3)
import Graphics.Implicit.ExtOpenScad.Definitions (OVal(ONum, OBool, OString, OList, OFunc, OUndefined, OModule, OError, OObj2, OObj3))
import qualified Control.Monad as Monad
import Data.Maybe (fromJust, isJust)
import Control.Monad (mapM, msum)
import Data.Maybe (fromMaybe, maybe)
-- for some minimal paralellism.
import Control.Parallel.Strategies(runEval, rpar, rseq)
-- | We'd like to be able to turn OVals into a given Haskell type
-- Convert OVals (and Lists of OVals) into a given Haskell type
class OTypeMirror a where
fromOObj :: OVal -> Maybe a
fromOObjList :: OVal -> Maybe [a]
fromOObjList (OList list) = mapM fromOObj list
fromOObjList _ = Nothing
{-# INLINABLE fromOObjList #-}
toOObj :: a -> OVal
instance OTypeMirror OVal where
fromOObj a = Just a
fromOObj = Just
toOObj a = a
instance OTypeMirror where
fromOObj (ONum n) = Just n
fromOObj _ = Nothing
toOObj n = ONum n
{-# INLINABLE fromOObj #-}
toOObj = ONum
instance OTypeMirror where
fromOObj (ONum n) = if n == fromInteger (floor n) then Just (floor n) else Nothing
fromOObj _ = Nothing
toOObj n = ONum $ fromIntegral n
{-# INLINABLE fromOObj #-}
toOObj = ONum . fromto
instance OTypeMirror Bool where
fromOObj (OBool b) = Just b
fromOObj _ = Nothing
toOObj b = OBool b
{-# INLINABLE fromOObj #-}
toOObj = OBool
#if __GLASGOW_HASKELL__ >= 710
instance {-# Overlapping #-} OTypeMirror String where
#else
instance OTypeMirror String where
#endif
fromOObj (OString str) = Just str
-- We don't actually use single chars, this is to compile lists of chars (AKA strings) after passing through OTypeMirror [a]'s fromOObj.
-- This lets us handle strings without overlapping the [a] case.
instance OTypeMirror Char where
fromOObj (OString str) = Just $ head str
fromOObj _ = Nothing
toOObj str = OString str
{-# INLINABLE fromOObj #-}
fromOObjList (OString str) = Just str
fromOObjList _ = Nothing
toOObj a = OString [a]
instance forall a. (OTypeMirror a) => OTypeMirror (Maybe a) where
instance (OTypeMirror a) => OTypeMirror [a] where
fromOObj = fromOObjList
{-# INLINABLE fromOObj #-}
toOObj list = OList $ map toOObj list
instance (OTypeMirror a) => OTypeMirror (Maybe a) where
fromOObj a = Just $ fromOObj a
{-# INLINABLE fromOObj #-}
toOObj (Just a) = toOObj a
toOObj Nothing = OUndefined
#if __GLASGOW_HASKELL__ >= 710
instance {-# Overlappable #-} forall a. (OTypeMirror a) => OTypeMirror [a] where
#else
instance forall a. (OTypeMirror a) => OTypeMirror [a] where
#endif
fromOObj (OList list) = Monad.sequence . map fromOObj $ list
fromOObj _ = Nothing
toOObj list = OList $ map toOObj list
instance forall a b. (OTypeMirror a, OTypeMirror b) => OTypeMirror (a,b) where
fromOObj (OList ((fromOObj -> Just a):(fromOObj -> Just b):[])) = Just (a,b)
instance (OTypeMirror a, OTypeMirror b) => OTypeMirror (a,b) where
fromOObj (OList [fromOObj -> Just a,fromOObj -> Just b]) = Just (a,b)
fromOObj _ = Nothing
{-# INLINABLE fromOObj #-}
toOObj (a,b) = OList [toOObj a, toOObj b]
instance forall a b c. (OTypeMirror a, OTypeMirror b, OTypeMirror c) => OTypeMirror (a,b,c) where
fromOObj (OList ((fromOObj -> Just a):(fromOObj -> Just b):(fromOObj -> Just c):[])) =
instance (OTypeMirror a, OTypeMirror b, OTypeMirror c) => OTypeMirror (a,b,c) where
fromOObj (OList [fromOObj -> Just a,fromOObj -> Just b,fromOObj -> Just c]) =
Just (a,b,c)
fromOObj _ = Nothing
{-# INLINABLE fromOObj #-}
toOObj (a,b,c) = OList [toOObj a, toOObj b, toOObj c]
instance forall a b. (OTypeMirror a, OTypeMirror b) => OTypeMirror (a -> b) where
instance (OTypeMirror a, OTypeMirror b) => OTypeMirror (a -> b) where
fromOObj (OFunc f) = Just $ \input ->
let
oInput = toOObj input
oOutput = f oInput
output :: Maybe b
output = fromOObj oOutput
in case output of
Just out -> out
Nothing -> error $ "coercing OVal to a -> b isn't always safe; use a -> Maybe b"
++ " (trace: " ++ show oInput ++ " -> " ++ show oOutput ++ " )"
in
fromMaybe (error $ "coercing OVal to a -> b isn't always safe; use a -> Maybe b"
++ " (trace: " ++ show oInput ++ " -> " ++ show oOutput ++ " )") output
fromOObj _ = Nothing
{-# INLINABLE fromOObj #-}
toOObj f = OFunc $ \oObj ->
case fromOObj oObj :: Maybe a of
Nothing -> OError ["bad input type"]
Just obj -> toOObj $ f obj
instance forall a b. (OTypeMirror a, OTypeMirror b) => OTypeMirror (Either a b) where
instance (OTypeMirror a, OTypeMirror b) => OTypeMirror (Either a b) where
fromOObj (fromOObj -> Just (x :: a)) = Just $ Left x
fromOObj (fromOObj -> Just (x :: b)) = Just $ Right x
fromOObj _ = Nothing
{-# INLINABLE fromOObj #-}
toOObj (Right x) = toOObj x
toOObj (Left x) = toOObj x
oTypeStr :: OVal -> [Char]
oTypeStr (OUndefined) = "Undefined"
-- A string representing each type.
oTypeStr :: OVal -> String
oTypeStr OUndefined = "Undefined"
oTypeStr (OBool _ ) = "Bool"
oTypeStr (ONum _ ) = "Number"
oTypeStr (OList _ ) = "List"
@ -121,7 +128,7 @@ oTypeStr (OObj3 _ ) = "3D Object"
getErrors :: OVal -> Maybe String
getErrors (OError er) = Just $ head er
getErrors (OList l) = Monad.msum $ map getErrors l
getErrors (OList l) = msum $ map getErrors l
getErrors _ = Nothing
caseOType :: forall c a. a -> (a -> c) -> c
@ -132,21 +139,20 @@ infixr 2 <||>
=> (desiredType -> out)
-> (OVal -> out)
-> (OVal -> out)
(<||>) f g = \input ->
(<||>) f g input =
let
coerceAttempt :: Maybe desiredType
coerceAttempt = fromOObj input
in
if isJust coerceAttempt -- ≅ (/= Nothing) but no Eq req
then f $ fromJust coerceAttempt
else g input
maybe (g input) f coerceAttempt
-- separate 2d and 3d objects from a set of OVals.
divideObjs :: [OVal] -> ([SymbolicObj2], [SymbolicObj3], [OVal])
divideObjs children =
runEval $ do
obj2s <- rseq ([ x | OObj2 x <- children ])
obj3s <- rseq ([ x | OObj3 x <- children ])
objs <- rpar (filter (not . isOObj) $ children )
obj2s <- rseq [ x | OObj2 x <- children ]
obj3s <- rseq [ x | OObj3 x <- children ]
objs <- rpar (filter (not . isOObj) children)
return (obj2s, obj3s, objs)
where
isOObj (OObj2 _) = True

View File

@ -7,58 +7,63 @@
-- FIXME: required. why?
{-# LANGUAGE KindSignatures, FlexibleContexts #-}
{-# LANGUAGE ViewPatterns, RankNTypes, ScopedTypeVariables #-}
{-# LANGUAGE RankNTypes, ScopedTypeVariables #-}
module Graphics.Implicit.ExtOpenScad.Util.StateC (getVarLookup, modifyVarLookup, lookupVar, pushVals, getVals, putVals, withPathShiftedBy, getPath, getRelPath, errorC, mapMaybeM, StateC) where
module Graphics.Implicit.ExtOpenScad.Util.StateC (getVarLookup, modifyVarLookup, lookupVar, pushVals, getVals, putVals, withPathShiftedBy, getPath, getRelPath, errorC, mapMaybeM, StateC, CompState(CompState)) where
import Prelude(FilePath, IO, String, Maybe(Just, Nothing), Show, Char, Monad, fmap, (.), ($), (++), return, putStrLn, show)
import Prelude(FilePath, IO, String, Maybe(Just, Nothing), Show, Monad, fmap, (.), ($), (++), return, putStrLn, show)
import Graphics.Implicit.ExtOpenScad.Definitions(VarLookup, OVal)
import qualified Data.Map as Map
import Data.Map (lookup)
import Control.Monad.State (StateT, get, put, modify, liftIO)
import System.FilePath((</>))
import Control.Monad.IO.Class (MonadIO)
import Data.Kind (Type)
-- | This is the state of a computation. It contains a hash of variables, an array of OVals, and a path.
newtype CompState = CompState (VarLookup, [OVal], FilePath)
type CompState = (VarLookup, [OVal], FilePath, (), ())
type StateC = StateT CompState IO
getVarLookup :: StateC VarLookup
getVarLookup = fmap (\(a,_,_,_,_) -> a) get
getVarLookup = fmap (\(CompState (a,_,_)) -> a) get
modifyVarLookup :: (VarLookup -> VarLookup) -> StateC ()
modifyVarLookup = modify . (\f (a,b,c,d,e) -> (f a, b, c, d, e))
modifyVarLookup = modify . (\f (CompState (a,b,c)) -> CompState (f a, b, c))
-- | Perform a variable lookup
lookupVar :: String -> StateC (Maybe OVal)
lookupVar name = do
varlookup <- getVarLookup
return $ Map.lookup name varlookup
return $ lookup name varlookup
pushVals :: [OVal] -> StateC ()
pushVals vals = modify (\(a,b,c,d,e) -> (a, vals ++ b,c,d,e))
pushVals vals = modify (\(CompState (a,b,c)) -> CompState (a, vals ++ b, c))
getVals :: StateC [OVal]
getVals = do
(_,b,_,_,_) <- get
(CompState (_,b,_)) <- get
return b
putVals :: [OVal] -> StateC ()
putVals vals = do
(a,_,c,d,e) <- get
put (a,vals,c,d,e)
(CompState (a,_,c)) <- get
put $ CompState (a,vals,c)
withPathShiftedBy :: FilePath -> StateC a -> StateC a
withPathShiftedBy pathShift s = do
(a,b,path,d,e) <- get
put (a,b, path </> pathShift, d, e)
(CompState (a,b,path)) <- get
put $ CompState (a, b, path </> pathShift)
x <- s
(a',b',_,d',e') <- get
put (a', b', path, d', e')
(CompState (a',b',_)) <- get
put $ CompState (a', b', path)
return x
-- | Return the path stored in the state.
getPath :: StateC FilePath
getPath = do
(_,_,c,_,_) <- get
(CompState (_,_,c)) <- get
return c
getRelPath :: FilePath -> StateC FilePath
@ -66,10 +71,11 @@ getRelPath relPath = do
path <- getPath
return $ path </> relPath
errorC :: forall (m :: * -> *) a. (Show a, MonadIO m) => a -> [Char] -> m ()
errorC lineN err = liftIO $ putStrLn $ "At " ++ show lineN ++ ": " ++ err
errorC :: forall (m :: Type -> Type) a. (Show a, MonadIO m) => a -> a -> String -> m ()
errorC lineN columnN err = liftIO $ putStrLn $ "On line " ++ show lineN ++ ", column " ++ show columnN ++ ": " ++ err
{-# INLINABLE errorC #-}
mapMaybeM :: forall t (m :: * -> *) a. Monad m => (t -> m a) -> Maybe t -> m (Maybe a)
mapMaybeM :: forall t (m :: Type -> Type) a. Monad m => (t -> m a) -> Maybe t -> m (Maybe a)
mapMaybeM f (Just a) = do
b <- f a
return (Just b)

View File

@ -0,0 +1,79 @@
-- Implicit CAD. Copyright (C) 2011, Christopher Olah (chris@colah.ca)
-- Copyright 2014-2019, Julia Longtin (julial@turinglace.com)
-- Released under the GNU AGPLV3+, see LICENSE
module Graphics.Implicit.FastIntUtil (Fast(Fast), toFast, fromFast) where
import Prelude (Integral(toInteger, quot, rem, quotRem, div, mod, divMod), Num((+), (*), abs, negate, signum, fromInteger), Eq, Ord, Enum(succ, pred, toEnum, fromEnum), Real(toRational), Show, ($), Read, Int)
import qualified Prelude as P ((+), (*), abs, negate, signum, fromInteger, succ, pred, toEnum, quotRem, divMod, toInteger)
import GHC.Real (Ratio((:%)))
class FastN n where
fromFast :: Fast -> n
toFast :: n -> Fast
instance FastN Int where
fromFast (Fast a) = a
{-# INLINABLE fromFast #-}
toFast a = Fast a
{-# INLINABLE toFast #-}
instance FastN Fast where
fromFast (Fast a) = Fast a
{-# INLINABLE fromFast #-}
toFast a = a
{-# INLINABLE toFast #-}
-- System integers, meant to go fast, and have no chance of wrapping 2^31.
newtype Fast = Fast Int
deriving (Show, Read, Eq, Ord)
instance Real Fast where
toRational (Fast a) = P.toInteger a :% 1
{-# INLINABLE toRational #-}
fastBoth :: (Int, Int) -> (Fast, Fast)
fastBoth (a, b) = (Fast a, Fast b)
{-# INLINABLE fastBoth #-}
instance Integral Fast where
toInteger (Fast a) = P.toInteger a
{-# INLINABLE toInteger #-}
quot (Fast n) (Fast d) = Fast $ q where (q,_) = quotRem n d
{-# INLINABLE quot #-}
rem (Fast n) (Fast d) = Fast $ r where (_,r) = quotRem n d
{-# INLINABLE rem #-}
quotRem (Fast a) (Fast b) = fastBoth $ P.quotRem a b
{-# INLINABLE quotRem #-}
div (Fast n) (Fast d) = Fast $ q where (q,_) = divMod n d
{-# INLINABLE div #-}
mod (Fast n) (Fast d) = Fast $ r where (_,r) = divMod n d
{-# INLINABLE mod #-}
divMod (Fast n) (Fast d) = fastBoth $ P.divMod n d
{-# INLINABLE divMod #-}
instance Num Fast where
(+) (Fast a) (Fast b) = Fast $ a P.+ b
{-# INLINABLE (+) #-}
(*) (Fast a) (Fast b) = Fast $ a P.* b
{-# INLINABLE (*) #-}
abs (Fast a) = Fast $ P.abs a
{-# INLINABLE abs #-}
negate (Fast a) = Fast $ P.negate a
{-# INLINABLE negate #-}
signum (Fast a) = Fast $ P.signum a
{-# INLINABLE signum #-}
fromInteger a = Fast $ P.fromInteger a
{-# INLINABLE fromInteger #-}
instance Enum Fast where
succ (Fast x) = Fast $ P.succ x
{-# INLINABLE succ #-}
pred (Fast x) = Fast $ P.pred x
{-# INLINABLE pred #-}
toEnum n = Fast $ P.toEnum n
{-# INLINABLE toEnum #-}
fromEnum (Fast n) = n
{-# INLINABLE fromEnum #-}

View File

@ -0,0 +1,97 @@
-- Implicit CAD. Copyright (C) 2011, Christopher Olah (chris@colah.ca)
-- Copyright 2014-2019, Julia Longtin (julial@turinglace.com)
-- Released under the GNU AGPLV3+, see LICENSE
-- Allow us to use explicit foralls when writing function type declarations.
{-# LANGUAGE ExplicitForAll #-}
module Graphics.Implicit.IntegralUtil (, to, from) where
import Prelude (Integral(toInteger, quot, rem, quotRem, div, mod, divMod), Num((+), (*), abs, negate, signum, fromInteger), Eq, Ord, Enum(succ, pred, toEnum, fromEnum), Real(toRational), Show, ($), Read, fromIntegral, Int, Integer)
import qualified Prelude as P ((+), (*), abs, negate, signum, succ, pred, toEnum, fromEnum, quotRem, divMod)
import GHC.Real (Ratio((:%)))
-- So we can produce an instance of Fast for .
import Graphics.Implicit.FastIntUtil (Fast(Fast))
-- the N typeclass. only used to define the type.
class (Integral n) => N n where
from :: -> n
to :: n ->
instance N Integer where
from ( a) = a
{-# INLINABLE from #-}
to a = a
{-# INLINABLE to #-}
instance N Fast where
from ( a) = Fast $ fromIntegral a
{-# INLINABLE from #-}
to a = $ fromIntegral a
{-# INLINABLE to #-}
instance N Int where
from ( a) = fromIntegral a
{-# INLINABLE from #-}
to a = $ fromIntegral a
{-# INLINABLE to #-}
-- Arbitrary precision integers. To be used for anything countable, or in ratios.
newtype = Integer
deriving (Show, Read, Eq, Ord)
instance Real where
toRational ( a) = a :% 1
{-# INLINABLE toRational #-}
both :: (Integer, Integer) -> (, )
both (a, b) = ( a , b)
instance Integral where
toInteger ( a) = a
{-# INLINABLE toInteger #-}
quot ( n) ( d) = $ q where (q,_) = quotRem n d
{-# INLINABLE quot #-}
rem ( n) ( d) = $ r where (_,r) = quotRem n d
{-# INLINABLE rem #-}
quotRem ( a) ( b) = both $ P.quotRem a b
{-# INLINABLE quotRem #-}
div ( n) ( d) = $ q where (q,_) = divMod n d
{-# INLINABLE div #-}
mod ( n) ( d) = $ r where (_,r) = divMod n d
{-# INLINABLE mod #-}
divMod ( n) ( d) = both $ P.divMod n d
{-# INLINABLE divMod #-}
instance Num where
(+) ( a) ( b) = $ a P.+ b
{-# INLINABLE (+) #-}
(*) ( a) ( b) = $ a P.* b
{-# INLINABLE (*) #-}
abs ( a) = $ P.abs a
{-# INLINABLE abs #-}
negate ( a) = $ P.negate a
{-# INLINABLE negate #-}
signum ( a) = $ P.signum a
{-# INLINABLE signum #-}
fromInteger a = a
{-# INLINABLE fromInteger #-}
-- | Note that we do not implement all of the members of the typeclass here.
instance Enum where
succ ( x) = $ P.succ x
{-# INLINABLE succ #-}
pred ( x) = $ P.pred x
{-# INLINABLE pred #-}
toEnum n = $ P.toEnum n
{-# INLINABLE toEnum #-}
fromEnum ( n) = P.fromEnum n
{-# INLINABLE fromEnum #-}

View File

@ -9,21 +9,27 @@
module Graphics.Implicit.MathUtil (rmax, rmaximum, rminimum, distFromLineSeg, pack, box3sWithin) where
-- Explicitly include what we need from Prelude.
import Prelude (Bool, Num, Ord, Ordering, (>), (<), (+), ($), (/), otherwise, not, (||), (&&), abs, (-), (*), sin, asin, pi, max, sqrt, min, compare, (<=), fst, snd, (++))
import Prelude (Bool, Num, Ord, Ordering, (>), (<), (+), ($), (/), otherwise, not, (||), (&&), abs, (-), (*), sin, asin, pi, max, sqrt, min, compare, (<=), fst, snd, (++), head, flip)
import Graphics.Implicit.Definitions (, 2, 3, Box2, ())
import Data.List (sort, sortBy, reverse, (!!))
import Data.List (sort, sortBy, (!!))
import Data.VectorSpace (magnitude, normalized, (^-^), (^+^), (*^))
import Data.AffineSpace ((.-.))
-- get the distance between two points.
import Data.AffineSpace (distance)
-- | The distance a point p is from a line segment (a,b)
distFromLineSeg :: 2 -> (2, 2) ->
distFromLineSeg p (a,b) = magnitude (closest .-. p)
distFromLineSeg p (a,b) = distance p closest
where
ab = b ^-^ a
ap = p ^-^ a
d = normalized ab ap
d ::
d = (normalized ab) ap
-- the closest point to p on the line segment.
closest :: 2
closest
| d < 0 = a
| d > magnitude ab = b
@ -70,26 +76,26 @@ rmaximum ::
-- ^ radius
-> [] -- ^ numbers to take round maximum
-> -- ^ resulting number
rmaximum _ (a:[]) = a
rmaximum r (a:b:[]) = rmax r a b
rmaximum _ [a] = a
rmaximum r [a,b] = rmax r a b
rmaximum r l =
let
tops = reverse $ sort l
tops = sortBy (flip compare) l
in
rmax r (tops !! 0) (tops !! 1)
rmax r (head tops) (tops !! 1)
-- | Like rmin but on a list.
rminimum ::
-- ^ radius
-> [] -- ^ numbers to take round minimum
-> -- ^ resulting number
rminimum _ (a:[]) = a
rminimum r (a:b:[]) = rmin r a b
rminimum _ [a] = a
rminimum r [a,b] = rmin r a b
rminimum r l =
let
tops = sort l
in
rmin r (tops !! 0) (tops !! 1)
rmin r (head tops) (tops !! 1)
-- | Pack the given objects in a box the given size.
pack ::
@ -107,9 +113,9 @@ pack (dx, dy) sep objs = packSome sortedObjs (dx, dy)
(\(boxa, _) (boxb, _) -> compareBoxesByY boxa boxb )
objs
tmap1 :: forall t t1 t2. (t2 -> t) -> (t2, t1) -> (t, t1)
tmap1 :: (t2 -> t) -> (t2, t1) -> (t, t1)
tmap1 f (a,b) = (f a, b)
tmap2 :: forall t t1 t2. (t2 -> t1) -> (t, t2) -> (t, t1)
tmap2 :: (t2 -> t1) -> (t, t2) -> (t, t1)
tmap2 f (a,b) = (a, f b)
packSome :: [(Box2,a)] -> Box2 -> ([(2,a)], [(Box2,a)])
@ -121,7 +127,7 @@ pack (dx, dy) sep objs = packSome sortedObjs (dx, dy)
packSome otherBoxedObjs ((bx1+x2-x1+sep, by1), (bx2, by1 + y2-y1))
rowAndUp =
if abs (by2-by1) - abs (y2-y1) > sep
then tmap1 ((fst row) ++ ) $
then tmap1 (fst row ++ ) $
packSome (snd row) ((bx1, by1 + y2-y1+sep), (bx2, by2))
else row
in

View File

@ -2,10 +2,11 @@
-- Copyright (C) 2014 2015 2016, Julia Longtin (julial@turinglace.com)
-- Released under the GNU AGPLV3+, see LICENSE
-- create a module that is just wrapping up these functions.
-- create a module that just wraps the functions in the ObjectUtil directory.
module Graphics.Implicit.ObjectUtil(getImplicit3, getImplicit2, getBox3, getBox2) where
-- as there is no real content here, we need no content from the prelude.
import Prelude()
import Graphics.Implicit.ObjectUtil.GetImplicit3 (getImplicit3)

View File

@ -2,7 +2,12 @@
-- Copyright 2016, Julia Longtin (julial@turinglace.com)
-- Released under the GNU AGPLV3+, see LICENSE
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances, FlexibleContexts, TypeSynonymInstances, UndecidableInstances #-}
-- FIXME: Document what these are for.
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE UndecidableInstances #-}
module Graphics.Implicit.ObjectUtil.GetBox2 (getBox2, getDist2) where
@ -14,12 +19,12 @@ import Graphics.Implicit.Definitions (, 2, Box2, (⋯*),
import Data.VectorSpace (magnitude, (^-^), (^+^))
-- Is a Box2 empty?
-- Really, this checks if it is one dimensional, which is good enough.
-- | Is a Box2 empty?
-- | Really, this checks if it is one dimensional, which is good enough.
isEmpty :: Box2 -> Bool
isEmpty ((a, b), (c, d)) = a==c || b==d
-- Define a Box2 around all of the given points.
-- | Define a Box2 around all of the given points.
pointsBox :: [2] -> Box2
pointsBox points =
let
@ -27,6 +32,7 @@ pointsBox points =
in
((minimum xs, minimum ys), (maximum xs, maximum ys))
-- | Define a box that fits around the given boxes.
unionBoxes :: [Box2] -> Box2
unionBoxes boxes =
let
@ -44,9 +50,8 @@ outsetBox r (a,b) =
getBox2 :: SymbolicObj2 -> Box2
-- Primitives
getBox2 (RectR _ a b) = (a,b)
getBox2 (Circle r ) = ((-r, -r), (r,r))
getBox2 (PolygonR _ points) = ((minimum xs, minimum ys), (maximum xs, maximum ys))
where (xs, ys) = unzip points
getBox2 (Circle r) = ((-r, -r), (r,r))
getBox2 (PolygonR _ points) = pointsBox points
-- (Rounded) CSG
getBox2 (Complement2 _) =
((-infty, -infty), (infty, infty))
@ -86,14 +91,13 @@ getBox2 (Scale2 s symbObj) =
getBox2 (Rotate2 θ symbObj) =
let
((x1,y1), (x2,y2)) = getBox2 symbObj
rotate (x,y) = (cos(θ)*x - sin(θ)*y, sin(θ)*x + cos(θ)*y)
rotate (x,y) = (x*(cos θ) - y*(sin θ), x*(sin θ) + y*(cos θ))
in
pointsBox [ rotate (x1, y1)
, rotate (x1, y2)
, rotate (x2, y1)
, rotate (x2, y2)
]
-- Boundary mods
getBox2 (Shell2 w symbObj) =
outsetBox (w/2) $ getBox2 symbObj
@ -105,20 +109,26 @@ getBox2 (EmbedBoxedObj2 (_,box)) = box
-- Get the maximum distance (read upper bound) an object is from a point.
-- Sort of a circular
getDist2 :: 2 -> SymbolicObj2 ->
-- Real implementations
getDist2 p (Circle r) = magnitude p + r
getDist2 p (PolygonR r points) = r + maximum [magnitude (p ^-^ p') | p' <- points]
-- Transform implementations
getDist2 p (UnionR2 r objs) = r + maximum [getDist2 p obj | obj <- objs ]
getDist2 p (DifferenceR2 r objs) = r + getDist2 p (head objs)
getDist2 p (IntersectR2 r objs) = r + maximum [getDist2 p obj | obj <- objs ]
-- FIXME: isn't this wrong? should we be returning distance inside of the object?
getDist2 _ (Complement2 _) = 1/0
getDist2 p (Translate2 v obj) = getDist2 (p ^+^ v) obj
getDist2 p (Circle r) = magnitude p + r
getDist2 p (PolygonR r points) =
r + maximum [magnitude (p ^-^ p') | p' <- points]
-- FIXME: write optimized functions for the rest of the SymbObjs.
-- Fallthrough: use getBox2 to check the distance a box is from the point.
getDist2 (x,y) symbObj =
let
((x1,y1), (x2,y2)) = getBox2 symbObj
in
sqrt (
(max (abs (x1 - x)) (abs (x2 - x))) *
(max (abs (x1 - x)) (abs (x2 - x))) +
(max (abs (y1 - y)) (abs (y2 - y))) *
(max (abs (y1 - y)) (abs (y2 - y)))
max (abs (x1 - x)) (abs (x2 - x)) *
max (abs (x1 - x)) (abs (x2 - x)) +
max (abs (y1 - y)) (abs (y2 - y)) *
max (abs (y1 - y)) (abs (y2 - y))
)

View File

@ -4,19 +4,23 @@
-- Released under the GNU AGPLV3+, see LICENSE
-- FIXME: required. why?
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances, FlexibleContexts, TypeSynonymInstances, UndecidableInstances #-}
{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FlexibleContexts, TypeSynonymInstances, UndecidableInstances #-}
module Graphics.Implicit.ObjectUtil.GetBox3 (getBox3) where
import Prelude(Eq, Bool(False), Fractional, Either (Left, Right), Maybe(Nothing, Just), (==), (||), max, (/), (-), (+), map, unzip, ($), filter, not, (.), unzip3, minimum, maximum, min, sqrt, (>), (&&), head, (*), (<), abs, either, error, const)
import Prelude(Eq, Bool(False), Fractional, Either (Left, Right), (==), (||), max, (/), (-), (+), map, unzip, ($), filter, not, (.), unzip3, minimum, maximum, min, (>), (&&), head, (*), (<), abs, either, error, const, otherwise, take)
import Graphics.Implicit.Definitions (, Box3, SymbolicObj3 (Rect3R, Sphere, Cylinder, Complement3, UnionR3, IntersectR3, DifferenceR3, Translate3, Scale3, Rotate3, Rotate3V, Shell3, Outset3, EmbedBoxedObj3, ExtrudeR, ExtrudeOnEdgeOf, ExtrudeRM, RotateExtrude, ExtrudeRotateR), (*))
import Data.Maybe(Maybe(Nothing, Just))
import Graphics.Implicit.Definitions (, Fast, Box3, SymbolicObj3 (Rect3R, Sphere, Cylinder, Complement3, UnionR3, IntersectR3, DifferenceR3, Translate3, Scale3, Rotate3, Rotate3V, Shell3, Outset3, EmbedBoxedObj3, ExtrudeR, ExtrudeOnEdgeOf, ExtrudeRM, RotateExtrude, ExtrudeRotateR), SymbolicObj2 (Rotate2, RectR), (*), fromFastto, fromFast)
import Graphics.Implicit.ObjectUtil.GetBox2 (getBox2, getDist2)
import Data.Maybe (fromMaybe)
import Data.VectorSpace ((^-^), (^+^))
-- test to see whether a Box3 has area.
-- FIXME: many variables are being ignored here. no rounding for intersect, or difference.. etc.
-- Test to see whether a Box3 has area.
isEmpty :: (Eq a2, Eq a1, Eq a) =>
((a, a1, a2), (a, a1, a2)) -> Bool
isEmpty ((a,b,c),(d,e,f)) = a==d || b==e || c==f
@ -29,7 +33,7 @@ outsetBox r (a,b) =
getBox3 :: SymbolicObj3 -> Box3
-- Primitives
getBox3 (Rect3R _ a b) = (a,b)
getBox3 (Sphere r ) = ((-r, -r, -r), (r,r,r))
getBox3 (Sphere r) = ((-r, -r, -r), (r,r,r))
getBox3 (Cylinder h r1 r2) = ( (-r,-r,0), (r,r,h) ) where r = max r1 r2
-- (Rounded) CSG
getBox3 (Complement3 _) =
@ -81,10 +85,17 @@ getBox3 (Scale3 s symbObj) =
(sbx,sby,sbz) = s * b
in
((min sax sbx, min say sby, min saz sbz), (max sax sbx, max say sby, max saz sbz))
getBox3 (Rotate3 _ symbObj) = ( (-d, -d, -d), (d, d, d) )
where
((x1,y1, z1), (x2,y2, z2)) = getBox3 symbObj
d = (sqrt 3 *) $ maximum $ map abs [x1, x2, y1, y2, z1, z2]
getBox3 (Rotate3 (a, b, c) symbObj) =
let
((x1, y1, z1), (x2, y2, z2)) = getBox3 symbObj
rotate v1 w1 v2 w2 angle = getBox2(Rotate2 angle $ RectR 0 (v1, w1) (v2, w2))
((y1', z1'), (y2', z2')) = rotate y1 z1 y2 z2 a
((z1'', x1'), (z2'', x2')) = rotate z1' x1 z2' x2 b
((x1'', y1''), (x2'', y2'')) = rotate x1' y1' x2' y2' c
(xs, ys, zs) = ([x1'', x2''], [y1'', y2''], [z1'', z2''])
in
((minimum xs, minimum ys, minimum zs), (maximum xs, maximum ys, maximum zs))
getBox3 (Rotate3V _ v symbObj) = getBox3 (Rotate3 v symbObj)
-- Boundary mods
getBox3 (Shell3 w symbObj) =
@ -103,22 +114,25 @@ getBox3 (ExtrudeOnEdgeOf symbObj1 symbObj2) =
((bx1,by1),(bx2,by2)) = getBox2 symbObj2
in
((bx1+ax1, by1+ax1, ay1), (bx2+ax2, by2+ax2, ay2))
-- FIXME: magic numbers in range.
-- FIXME: magic numbers.
getBox3 (ExtrudeRM _ twist scale translate symbObj eitherh) =
let
range :: []
range = [0, 0.1 .. 1.0]
samples :: Fast
samples=11
hfuzz ::
hfuzz = 0.2
range :: [Fast]
range = [0, 1 .. (samples-1)]
((x1,y1),(x2,y2)) = getBox2 symbObj
(dx,dy) = (x2 - x1, y2 - y1)
(xrange, yrange) = (map (\s -> x1+s*dx) $ range, map (\s -> y1+s*dy) $ range )
(xrange, yrange) = ( map (\s -> x1+s*dx/(fromFastto $ samples-1)) $ map fromFastto range, map (\s -> y1+s*dy/(fromFastto $ samples-1)) $ map fromFastto range )
h = case eitherh of
Left h' -> h'
Right hf -> hmax + 0.2*(hmax-hmin)
Right hf -> hmax + hfuzz*(hmax-hmin)
where
hs = [hf (x,y) | x <- xrange, y <- yrange]
(hmin, hmax) = (minimum hs, maximum hs)
hrange = map (h*) $ range
hrange = map (/(fromFastto $ samples-1)) $ map (h*) $ map fromFastto range
sval = case scale of
Nothing -> 1
Just scale' -> maximum $ map (abs . scale') hrange
@ -130,7 +144,7 @@ getBox3 (ExtrudeRM _ twist scale translate symbObj eitherh) =
Just _ -> (-d, -d, d, d)
where d = sval * getDist2 (0,0) symbObj
translate' = fromMaybe (const (0,0)) translate
(tvalsx, tvalsy) = unzip . map (translate' . (h*)) $ hrange
(tvalsx, tvalsy) = unzip $ map (translate' . (h*)) hrange
(tminx, tminy) = (minimum tvalsx, minimum tvalsy)
(tmaxx, tmaxy) = (maximum tvalsx, maximum tvalsy)
in
@ -143,16 +157,28 @@ getBox3 (RotateExtrude _ _ (Left (xshift,yshift)) _ symbObj) =
r = max x2 (x2 + xshift)
in
((-r, -r, min y1 (y1 + yshift)),(r, r, max y2 (y2 + yshift)))
-- FIXME: magic numbers.
getBox3 (RotateExtrude rot _ (Right f) rotate symbObj) =
let
samples :: Fast
samples = 11
xfuzz ::
xfuzz = 1.1
yfuzz ::
yfuzz=0.1
range :: [Fast]
range = [0, 1 .. (samples-1)]
step = rot/(fromFastto $ samples-1)
((x1,y1),(x2,y2)) = getBox2 symbObj
(xshifts, yshifts) = unzip [f θ | θ <- [0 , rot / 10 .. rot] ]
xmax = maximum xshifts
ymax = maximum yshifts
ymin = minimum yshifts
xmax' = if xmax > 0 then xmax * 1.1 else if xmax < - x1 then 0 else xmax
ymax' = ymax + 0.1 * (ymax - ymin)
ymin' = ymin - 0.1 * (ymax - ymin)
(xrange, yrange) = unzip $ take (fromFast samples) $ map f $ map (step*) $ map fromFastto range
xmax = maximum xrange
ymax = maximum yrange
ymin = minimum yrange
xmax' | xmax > 0 = xmax * xfuzz
| xmax < - x1 = 0
| otherwise = xmax
ymax' = ymax + yfuzz * (ymax - ymin)
ymin' = ymin - yfuzz * (ymax - ymin)
(r, _, _) = if either (==0) (const False) rotate
then let
s = maximum $ map abs [x2, y1, y2]
@ -161,4 +187,4 @@ getBox3 (RotateExtrude rot _ (Right f) rotate symbObj) =
in
((-r, -r, y1 + ymin'),(r, r, y2 + ymax'))
-- FIXME: add case for ExtrudeRotateR!
getBox3(ExtrudeRotateR _ _ _ _ ) = error "ExtrudeRotateR implementation incomplete!"
getBox3 ExtrudeRotateR{} = error "ExtrudeRotateR implementation incomplete!"

View File

@ -10,94 +10,102 @@
module Graphics.Implicit.ObjectUtil.GetImplicit2 (getImplicit2) where
import Prelude(Int, Num, abs, (-), (/), sqrt, (*), (+), (!!), mod, length, map, (<=), (&&), (>=), (||), odd, ($), (>), filter, (<), minimum, (==), maximum, max, cos, sin, head, tail)
import Prelude(Num, abs, (-), (/), sqrt, (*), (+), mod, length, map, (<=), (&&), (>=), (||), odd, ($), (>), filter, (<), minimum, (==), maximum, max, cos, sin, head, tail, (.))
import Graphics.Implicit.Definitions (, , 2, (/), Obj2, SymbolicObj2(RectR, Circle, PolygonR, Complement2, UnionR2, DifferenceR2, IntersectR2, Translate2, Scale2, Rotate2, Shell2, Outset2, EmbedBoxedObj2))
import Graphics.Implicit.Definitions (SymbolicObj2(RectR, Circle, PolygonR, Complement2, UnionR2, DifferenceR2, IntersectR2, Translate2, Scale2, Rotate2, Shell2, Outset2, EmbedBoxedObj2), Obj2, , 2, (/))
import Graphics.Implicit.MathUtil (rminimum, rmaximum, distFromLineSeg)
import Data.VectorSpace ((^-^))
import Data.List (nub)
import Data.List (nub, genericIndex, genericLength)
getImplicit2 :: SymbolicObj2 -> Obj2
-- Primitives
getImplicit2 (RectR r (x1,y1) (x2,y2)) = \(x,y) -> rmaximum r
[abs (x-dx/2-x1) - dx/2, abs (y-dy/2-y1) - dy/2]
where (dx, dy) = (x2-x1, y2-y1)
getImplicit2 (Circle r ) =
\(x,y) -> sqrt (x * x + y * y) - r
getImplicit2 (RectR r (x1,y1) (x2,y2)) =
\(x,y) -> let
(dx, dy) = (x2-x1, y2-y1)
in
if r == 0
then maximum [(abs $ x-dx/2-x1) - dx/2, (abs $ y-dy/2-y1) - dy/2]
else rmaximum r [(abs $ x-dx/2-x1) - dx/2, (abs $ y-dy/2-y1) - dy/2]
getImplicit2 (Circle r) =
\(x,y) -> (sqrt $ x * x + y * y) - r
getImplicit2 (PolygonR _ points) =
\p -> let
pair :: Int -> (2,2)
pair n = (points !! n, points !! (mod (n + 1) (length points) ) )
pairs = [ pair n | n <- [0 .. (length points) - 1] ]
pair :: -> (2,2)
pair n = (points `genericIndex` n, points `genericIndex` mod (n + 1) (genericLength points) )
pairs :: [(2,2)]
pairs = [ pair n | n <- [0 .. genericLength points - 1] ]
relativePairs = map (\(a,b) -> (a ^-^ p, b ^-^ p) ) pairs
crossing_points =
[x2 ^-^ y2*(x2-x1)/(y2-y1) | ((x1,y1), (x2,y2)) <-relativePairs,
( (y2 <= 0) && (y1 >= 0) ) || ( (y2 >= 0) && (y1 <= 0) ) ]
seemsInRight = odd $ length $ filter (>0) $ nub crossing_points
seemsInLeft = odd $ length $ filter (<0) $ nub crossing_points
-- FIXME: use partition instead?
seemsInRight = odd . length . filter (>0) $ nub crossing_points
seemsInLeft = odd . length . filter (<0) $ nub crossing_points
isIn = seemsInRight && seemsInLeft
dists = map (distFromLineSeg p) pairs :: []
dists :: []
dists = map (distFromLineSeg p) pairs
in
minimum dists * if isIn then -1 else 1
-- (Rounded) CSG
getImplicit2 (Complement2 symbObj) =
let
\p -> let
obj = getImplicit2 symbObj
in
\p -> - obj p
- obj p
getImplicit2 (UnionR2 r symbObjs) =
let
\p -> let
objs = map getImplicit2 symbObjs
in
if r == 0
then \p -> minimum $ map ($p) objs
else \p -> rminimum r $ map ($p) objs
then minimum $ map ($p) objs
else rminimum r $ map ($p) objs
getImplicit2 (DifferenceR2 r symbObjs) =
let
objs = map getImplicit2 symbObjs
obj = head objs
complement :: forall a t. Num a => (t -> a) -> t -> a
complement obj' = \p -> - obj' p
complement obj' p = - obj' p
in
if r == 0
then \p -> maximum $ map ($p) $ obj:(map complement $ tail objs)
else \p -> rmaximum r $ map ($p) $ obj:(map complement $ tail objs)
then \p -> maximum . map ($p) $ obj:map complement (tail objs)
else \p -> rmaximum r . map ($p) $ obj:map complement (tail objs)
getImplicit2 (IntersectR2 r symbObjs) =
let
\p -> let
objs = map getImplicit2 symbObjs
in
if r == 0
then \p -> maximum $ map ($p) objs
else \p -> rmaximum r $ map ($p) objs
then maximum $ map ($p) objs
else rmaximum r $ map ($p) objs
-- Simple transforms
getImplicit2 (Translate2 v symbObj) =
let
\p -> let
obj = getImplicit2 symbObj
in
\p -> obj (p ^-^ v)
obj (p ^-^ v)
getImplicit2 (Scale2 s@(sx,sy) symbObj) =
let
\p -> let
obj = getImplicit2 symbObj
k = abs(max sx sy)
k = abs $ max sx sy
in
\p -> k * obj (p / s)
k * obj (p / s)
getImplicit2 (Rotate2 θ symbObj) =
let
\(x,y) -> let
obj = getImplicit2 symbObj
in
\(x,y) -> obj ( cos(θ)*x + sin(θ)*y, cos(θ)*y - sin(θ)*x)
obj ( x*(cos θ) + y*(sin θ), y*(cos θ) - x*(sin θ))
-- Boundary mods
getImplicit2 (Shell2 w symbObj) =
let
\p -> let
obj = getImplicit2 symbObj
in
\p -> abs (obj p) - w/2
(abs $ obj p) - w/2
getImplicit2 (Outset2 d symbObj) =
let
\p -> let
obj = getImplicit2 symbObj
in
\p -> obj p - d
obj p - d
-- Misc
getImplicit2 (EmbedBoxedObj2 (obj,_)) = obj

View File

@ -8,32 +8,41 @@
module Graphics.Implicit.ObjectUtil.GetImplicit3 (getImplicit3) where
import Prelude (Either(Left, Right), Int, abs, (-), (/), (*), sqrt, (+), atan2, max, cos, map, (==), minimum, ($), maximum, (**), sin, const, pi, (.), Bool(True, False), ceiling, floor, fromIntegral, return, error, head, tail, Num)
import Prelude (Either(Left, Right), abs, (-), (/), (*), sqrt, (+), atan2, max, cos, map, (==), minimum, ($), maximum, (**), sin, const, pi, (.), Bool(True, False), ceiling, floor, return, error, head, tail, Num)
import Graphics.Implicit.Definitions (, 2, 3, (/), Obj3,
import Graphics.Implicit.Definitions (, , 2, 3, (/), Obj3,
SymbolicObj3(Shell3, UnionR3, IntersectR3, DifferenceR3, Translate3, Scale3, Rotate3,
Outset3, Rect3R, Sphere, Cylinder, Complement3, EmbedBoxedObj3, Rotate3V,
ExtrudeR, ExtrudeRM, ExtrudeOnEdgeOf, RotateExtrude, ExtrudeRotateR))
ExtrudeR, ExtrudeRM, ExtrudeOnEdgeOf, RotateExtrude, ExtrudeRotateR), fromto)
import Graphics.Implicit.MathUtil (rmaximum, rminimum, rmax)
import qualified Data.Maybe as Maybe
import qualified Data.Either as Either
import Data.Maybe (fromMaybe, isJust)
import qualified Data.Either as Either (either)
import Data.VectorSpace ((^-^), (^+^), (^*), (<.>), normalized)
import Data.Cross (cross3)
-- Use getImplicit2 for handling extrusion of 2D shapes to 3D.
import Graphics.Implicit.ObjectUtil.GetImplicit2 (getImplicit2)
-- Get a function that describes the surface of the object.
getImplicit3 :: SymbolicObj3 -> Obj3
-- Primitives
getImplicit3 (Rect3R r (x1,y1,z1) (x2,y2,z2)) = \(x,y,z) -> rmaximum r
[abs (x-dx/2-x1) - dx/2, abs (y-dy/2-y1) - dy/2, abs (z-dz/2-z1) - dz/2]
where (dx, dy, dz) = (x2-x1, y2-y1, z2-z1)
getImplicit3 (Rect3R r (x1,y1,z1) (x2,y2,z2)) =
\(x,y,z) -> let (dx, dy, dz) = (x2-x1, y2-y1, z2-z1)
in
rmaximum r [(abs $ x-dx/2-x1) - dx/2, (abs $ y-dy/2-y1) - dy/2, (abs $ z-dz/2-z1) - dz/2]
getImplicit3 (Sphere r ) =
\(x,y,z) -> sqrt (x*x + y*y + z*z) - r
\(x,y,z) -> (sqrt $ x*x + y*y + z*z) - r
getImplicit3 (Cylinder h r1 r2) = \(x,y,z) ->
let
d = sqrt(x*x + y*y) - ((r2-r1)/h*z+r1)
d = (sqrt $ x*x + y*y) - ((r2-r1)/h*z+r1)
θ = atan2 (r2-r1) h
in
max (d * cos θ) (abs(z-h/(2::)) - h/(2::))
max (d * (cos θ)) ((abs $ z-h/2) - (h/2))
-- (Rounded) CSG
getImplicit3 (Complement3 symbObj) =
let
@ -59,11 +68,11 @@ getImplicit3 (DifferenceR3 r symbObjs) =
objs = map getImplicit3 symbObjs
obj = head objs
complement :: forall a t. Num a => (t -> a) -> t -> a
complement obj' = \p -> - obj' p
complement obj' p = - obj' p
in
if r == 0
then \p -> maximum $ map ($p) $ obj:(map complement $ tail objs)
else \p -> rmaximum r $ map ($p) $ obj:(map complement $ tail objs)
then \p -> maximum $ map ($p) $ obj:map complement (tail objs)
else \p -> rmaximum r $ map ($p) $ obj:map complement (tail objs)
-- Simple transforms
getImplicit3 (Translate3 v symbObj) =
let
@ -73,40 +82,35 @@ getImplicit3 (Translate3 v symbObj) =
getImplicit3 (Scale3 s@(sx,sy,sz) symbObj) =
let
obj = getImplicit3 symbObj
k = abs(sx*sy*sz)**(1/3)
k = (abs $ sx*sy*sz)**(1/3)
in
\p -> k * obj (p / s)
getImplicit3 (Rotate3 (yz, zx, xy) symbObj) =
let
obj = getImplicit3 symbObj
rotateYZ :: -> (3 -> ) -> (3 -> )
rotateYZ θ obj' = \(x,y,z) -> obj' ( x, cos(θ)*y + sin(θ)*z, cos(θ)*z - sin(θ)*y)
rotateYZ θ obj' (x,y,z) = obj' ( x, y*(cos θ) + z*(sin θ), z*(cos θ) - y*(sin θ))
rotateZX :: -> (3 -> ) -> (3 -> )
rotateZX θ obj' = \(x,y,z) -> obj' ( cos(θ)*x - sin(θ)*z, y, cos(θ)*z + sin(θ)*x)
rotateZX θ obj' (x,y,z) = obj' ( x*(cos θ) - z*(sin θ), y, z*(cos θ) + x*(sin θ))
rotateXY :: -> (3 -> ) -> (3 -> )
rotateXY θ obj' = \(x,y,z) -> obj' ( cos(θ)*x + sin(θ)*y, cos(θ)*y - sin(θ)*x, z)
rotateXY θ obj' (x,y,z) = obj' ( x*(cos θ) + y*(sin θ), y*(cos θ) - x*(sin θ), z)
in
rotateYZ yz $ rotateZX zx $ rotateXY xy $ obj
rotateXY xy $ rotateZX zx $ rotateYZ yz obj
getImplicit3 (Rotate3V θ axis symbObj) =
let
axis' = normalized axis
obj = getImplicit3 symbObj
-- Note: this is ripped from data.cross.
cross3 :: forall t. Num t => (t, t, t) -> (t, t, t) -> (t, t, t)
cross3 (ax,ay,az) (bx,by,bz) = ( ay * bz - az * by
, az * bx - ax * bz
, ax * by - ay * bx )
in
\v -> obj $
v ^* cos(θ)
^-^ (axis' `cross3` v) ^* sin(θ)
^+^ (axis' ^* (axis' <.> (v ^* (1 - cos(θ)))))
v ^* (cos θ)
^-^ (axis' `cross3` v) ^* (sin θ)
^+^ (axis' ^* (axis' <.> (v ^* (1 - (cos θ)))))
-- Boundary mods
getImplicit3 (Shell3 w symbObj) =
let
obj = getImplicit3 symbObj
in
\p -> abs (obj p) - w/2
\p -> (abs $ obj p) - w/2
getImplicit3 (Outset3 d symbObj) =
let
obj = getImplicit3 symbObj
@ -119,26 +123,27 @@ getImplicit3 (ExtrudeR r symbObj h) =
let
obj = getImplicit2 symbObj
in
\(x,y,z) -> rmax r (obj (x,y)) (abs (z - h/2) - h/2)
\(x,y,z) -> rmax r (obj (x,y)) ((abs $ z - h/2) - h/2)
getImplicit3 (ExtrudeRM r twist scale translate symbObj height) =
let
obj = getImplicit2 symbObj
twist' = Maybe.fromMaybe (const 0) twist
scale' = Maybe.fromMaybe (const 1) scale
translate' = Maybe.fromMaybe (const (0,0)) translate
twist' = fromMaybe (const 0) twist
scale' = fromMaybe (const 1) scale
translate' = fromMaybe (const (0,0)) translate
height' (x,y) = case height of
Left n -> n
Right f -> f (x,y)
scaleVec :: -> 2 -> 2
scaleVec s = \(x,y) -> (x/s, y/s)
scaleVec s (x,y) = (x/s, y/s)
rotateVec :: -> 2 -> 2
rotateVec θ (x,y) = (x*cos(θ)+y*sin(θ), y*cos(θ)-x*sin(θ))
k = (pi :: )/(180:: )
rotateVec θ (x,y) = (x*(cos θ) + y*(sin θ), y*(cos θ) - x*(sin θ))
k ::
k = pi/180
in
\(x,y,z) -> let h = height' (x,y) in
rmax r
(obj . rotateVec (-k*twist' z) . scaleVec (scale' z) . (\a -> a ^-^ translate' z) $ (x,y))
(abs (z - h/2) - h/2)
((abs $ z - h/2) - h/2)
getImplicit3 (ExtrudeOnEdgeOf symbObj1 symbObj2) =
let
obj1 = getImplicit2 symbObj1
@ -153,16 +158,16 @@ getImplicit3 (RotateExtrude totalRotation round translate rotate symbObj) =
k = tau / 360
totalRotation' = totalRotation*k
obj = getImplicit2 symbObj
capped = Maybe.isJust round
round' = Maybe.fromMaybe 0 round
capped = isJust round
round' = fromMaybe 0 round
translate' :: -> 2
translate' = Either.either
(\(a,b) -> \θ -> (a*θ/totalRotation', b*θ/totalRotation'))
(\(a,b) θ -> (a*θ/totalRotation', b*θ/totalRotation'))
(. (/k))
translate
rotate' :: ->
rotate' = Either.either
(\t -> \θ -> t*θ/totalRotation' )
(\t θ -> t*θ/totalRotation' )
(. (/k))
rotate
twists = case rotate of
@ -170,25 +175,25 @@ getImplicit3 (RotateExtrude totalRotation round translate rotate symbObj) =
_ -> False
in
\(x,y,z) -> minimum $ do
let
r = sqrt (x*x + y*y)
r = sqrt $ x*x + y*y
θ = atan2 y x
ns :: [Int]
ns :: []
ns =
if capped
then -- we will cap a different way, but want leeway to keep the function cont
[-1 .. (ceiling (totalRotation' / tau)) + 1]
[-1 .. ceiling (totalRotation' / tau) + 1]
else
[0 .. floor $ (totalRotation' - θ) /tau]
[0 .. floor $ (totalRotation' - θ) / tau]
n <- ns
let
θvirt = fromIntegral n * tau + θ
θvirt = (fromto n) * tau + θ
(rshift, zshift) = translate' θvirt
twist = rotate' θvirt
rz_pos = if twists
then let
(c,s) = (cos(twist*k), sin(twist*k))
(c,s) = ((cos $ twist*k), (sin $ twist*k))
(r',z') = (r-rshift, z-zshift)
in
(c*r' - s*z', c*z' + s*r')
@ -196,9 +201,9 @@ getImplicit3 (RotateExtrude totalRotation round translate rotate symbObj) =
return $
if capped
then rmax round'
(abs (θvirt - (totalRotation' / 2)) - (totalRotation' / 2))
((abs $ θvirt - (totalRotation' / 2)) - (totalRotation' / 2))
(obj rz_pos)
else obj rz_pos
-- FIXME: implement this, or implement a fallthrough function.
--getImplicit3 (ExtrudeRotateR) =
getImplicit3 (ExtrudeRotateR _ _ _ _) = error "ExtrudeRotateR unimplimented!"
getImplicit3 ExtrudeRotateR{} = error "ExtrudeRotateR unimplimented!"

View File

@ -138,12 +138,12 @@ polygonR = PolygonR
-- $ Shared Operations
class Object obj vec | obj -> vec where
-- | Complement an Object
complement ::
obj -- ^ Object to complement
-> obj -- ^ Result
-- | Rounded union
unionR ::
-- ^ The radius of rounding
@ -161,7 +161,7 @@ class Object obj vec | obj -> vec where
-- ^ The radius of rounding
-> [obj] -- ^ Objects to intersect
-> obj -- ^ Resulting object
-- | Translate an object by a vector of appropriate dimension.
translate ::
vec -- ^ Vector to translate by (Also: a is a vector, blah, blah)
@ -200,7 +200,7 @@ class Object obj vec | obj -> vec where
(vec -> ) -- ^ Implicit function
-> (vec, vec) -- ^ Bounding box
-> obj -- ^ Resulting object
instance Object SymbolicObj2 2 where
translate = Translate2
@ -265,7 +265,7 @@ rotateExtrude = RotateExtrude
extrudeOnEdgeOf :: SymbolicObj2 -> SymbolicObj2 -> SymbolicObj3
extrudeOnEdgeOf = ExtrudeOnEdgeOf
rotate3 :: (, , ) -> SymbolicObj3 -> SymbolicObj3
rotate3 :: 3 -> SymbolicObj3 -> SymbolicObj3
rotate3 = Rotate3
rotate3V :: -> 3 -> SymbolicObj3 -> SymbolicObj3
@ -275,7 +275,7 @@ rotate3V = Rotate3V
pack3 :: 2 -> -> [SymbolicObj3] -> Maybe SymbolicObj3
pack3 (dx, dy) sep objs =
let
boxDropZ :: forall t t1 t2 t3 t4 t5. ((t2, t3, t), (t4, t5, t1)) -> ((t2, t3), (t4, t5))
boxDropZ :: (3,3) -> (2,2)
boxDropZ ((a,b,_),(d,e,_)) = ((a,b),(d,e))
withBoxes :: [(Box2, SymbolicObj3)]
withBoxes = map (\obj -> ( boxDropZ $ getBox3 obj, obj)) objs

136
Makefile
View File

@ -1,65 +1,137 @@
.PHONY: build install clean docs dist test examples tests
# ImplicitCAD Makefile. Build and test Implicitcad.
RTSOPTS=+RTS -N
RESOPTS=-r 10
#uncomment for profiling support.
#PROFILING= --enable-library-profiling --enable-executable-profiling
# stl2ps, from stltools, available from https://github.com/rsmith-nl/stltools/tree/develop
## Locations of binaries used when running tests, or generating the images to go along with our README.md.
# The location of stl2ps, from stltools, available from https://github.com/rsmith-nl/stltools/tree/develop
stl2ps=/disk4/faikvm.com/stltools/stltools/stl2ps.py
# convert, from imagemagick
# The location of convert, from imagemagick
convert=convert
# The location of GHC, used to compile .hs examples.
GHC=ghc
# The location of the created extopenscad binary, for running shell based test cases.
EXTOPENSCAD=dist/build/extopenscad/extopenscad
# The location of the implicitsnap binary, which listens for requests via http. The backend of the website.
IMPLICITSNAP=dist/build/implicitsnap/implicitsnap
# The location of the benchmark binary, for benchmarking some implicitcad internals.
BENCHMARK=dist/build/Benchmark/Benchmark
# The location of the parser benchmark binary, specifically for benchmarking implicitcad's parser.
PARSERBENCH=dist/build/parser-bench/parser-bench
# The location of the created test binary, for running haskell test cases.
TESTSUITE=dist/build/test-implicit/test-implicit
# The location of it's source.
TESTFILES=$(shell find tests/ParserSpec -name '*.hs')
# The location of the documentation generator. for documenting (some of) the extopenscad language.
DOCGEN=dist/build/docgen/docgen
# FIXME: this used to be ./Setup install. what's going on?
install: $(EXTOPENSCAD)
## Options used when calling ImplicitCAD. for testing, and for image generation.
# Enable multiple CPU usage.
# Use the parallel garbage collector.
# spit out some performance statistics.
RTSOPTS=+RTS -N -qg -t
# The resolution to generate objects at. FIXME: what does this mean in human terms?
RESOPTS=-r 50
# Uncomment for profiling support. Note that you will need to recompile all of the libraries, as well.
#PROFILING= --enable-profiling
## FIXME: escape this right
# Uncomment for valgrind on the examples.
#VALGRIND=valgrind --tool=cachegrind --cachegrind-out-file=$$each.cachegrind.`date +%s`
LIBFILES=$(shell find Graphics -name '*.hs')
LIBTARGET=dist/build/Graphics/Implicit.o
EXECTARGETS=$(EXTOPENSCAD) $(IMPLICITSNAP) $(BENCHMARK) $(TESTSUITE) $(PARSERBENCH) $(DOCGEN)
TARGETS=$(EXECTARGETS) $(LIBTARGET)
# Mark the below fake targets as unreal, so make will not get choked up if a file with one of these names is created.
.PHONY: build install clean distclean nukeclean docs dist examples tests
# Empty out the default suffix list, to make debugging output cleaner.
.SUFFIXES:
# Allow for us to (ab)use $$* in dependencies of rules.
.SECONDEXPANSION:
# Disable make's default builtin rules, to make debugging output cleaner.
MAKEFLAGS += --no-builtin-rules
# Build implicitcad binaries.
build: $(TARGETS)
# Install implicitcad.
install: build
cabal install
# Cleanup from using the rules in this file.
clean: Setup
./Setup clean
rm -f Examples/*.stl
rm -f Examples/*.svg
rm -f Examples/*.ps
rm -f Examples/*.png
rm -f Examples/example[0-9][0-9]
rm -f Examples/*.hi
rm -f Examples/*.o
rm -f tests/*.stl
rm -f Setup Setup.hi Setup.o
rm -rf docs/parser.md
rm -f $(TARGETS)
rm -rf dist/build/Graphics
rm -f dist/build/libHS*
rm -f Examples/example*.cachegrind.*
distclean: clean
# Clean up before making a release.
distclean: clean Setup
./Setup clean
rm -f Setup Setup.hi Setup.o
rm -rf dist/
rm -f `find ./ -name *~`
rm -f `find ./ -name \#*\#`
# Destroy the current user's cabal/ghc environment.
nukeclean: distclean
rm -rf ~/.cabal/ ~/.ghc/
docs: $(EXTOPENSCAD)
# Generate documentation.
docs: $(DOCGEN)
./Setup haddock
$(DOCGEN) > docs/escad.md
dist: $(EXTOPENSCAD)
# Upload to hackage?
dist: $(TARGETS)
./Setup sdist
test: $(EXTOPENSCAD)
./Setup test
# Generate examples.
examples: $(EXTOPENSCAD)
cd Examples && for each in `find ./ -name '*scad' -type f | sort`; do { time ../$(EXTOPENSCAD) $$each ${RTSOPTS}; } done
cd Examples && for each in `find ./ -name '*.hs' -type f | sort`; do { filename=$(basename "$$each"); filename="$${filename%.*}"; ghc $$filename.hs -o $$filename; $$filename; } done
cd Examples && for each in `find ./ -name '*scad' -type f | sort`; do { ../$(EXTOPENSCAD) $$each $(RTSOPTS); } done
cd Examples && for each in `find ./ -name '*.hs' -type f | sort`; do { filename=$(basename "$$each"); filename="$${filename%.*}"; cd ..; $(GHC) Examples/$$filename.hs -o Examples/$$filename; cd Examples; echo $$filename; $$filename +RTS -t ; } done
images:
# Generate images from the examples, so we can upload the images to our website.
images: examples
cd Examples && for each in `find ./ -name '*.stl' -type f | sort`; do { filename=$(basename "$$each"); filename="$${filename%.*}"; if [ -e $$filename.transform ] ; then echo ${stl2ps} $$each $$filename.ps `cat $$filename.transform`; else ${stl2ps} $$each $$filename.ps; fi; ${convert} $$filename.ps $$filename.png; } done
tests: $(EXTOPENSCAD)
cd tests && for each in `find ./ -name '*scad' -type f | sort`; do { time ../$(EXTOPENSCAD) $$each ${RESOPTS} ${RTSOPTS}; } done
# Hspec parser tests.
tests: $(TESTSUITE) $(TESTFILES)
# cd tests && for each in `find ./ -name '*scad' -type f | sort`; do { ../$(EXTOPENSCAD) $$each ${RESOPTS} ${RTSOPTS}; } done
$(TESTSUITE)
dist/build/extopenscad/extopenscad: Setup dist/setup-config
cabal build
# The ImplicitCAD library.
$(LIBTARGET): $(LIBFILES)
cabal build implicit
# The parser test suite, since it's source is stored in a different location than the other binaries we build:
dist/build/test-implicit/test-implicit: $(TESTFILES) Setup dist/setup-config $(LIBTARGET) $(LIBFILES)
cabal build test-implicit
# Build a binary target with cabal.
dist/build/%: programs/$$(word 2,$$(subst /, ,%)).hs Setup dist/setup-config $(LIBTARGET) $(LIBFILES)
cabal build $(word 2,$(subst /, ,$*))
# Prepare to build.
dist/setup-config: Setup implicit.cabal
cabal install --only-dependencies
cabal configure $(PROFILING)
cabal update
cabal install --only-dependencies --upgrade-dependencies $(PROFILING)
cabal configure --enable-tests --enable-benchmarks $(PROFILING)
# The setup command, used to perform administrative tasks (haddock, upload to hackage, clean, etc...).
Setup: Setup.*hs
ghc -O2 -Wall --make Setup
$(GHC) -O2 -Wall --make Setup

View File

@ -146,7 +146,7 @@ linear_extrude (height = 40, center=true, twist=90, r=5){
![A rounded twisted extrusion](http://faikvm.com/ImplicitCAD/example7.png)
ImplicitCAD also provides full programmatic functionality, like variable assignment in loops, which are sadly absent in OpenSCAD. For example, the trivial program:
ImplicitCAD also provides full programmatic functionality, like variable assignment in loops. For example, the trivial program:
```c
// Example8.escad -- variable assignment in loops.

34
Release.md Normal file
View File

@ -0,0 +1,34 @@
# Release Processes:
Purpose of this document: to make sure i follow a consistent patern, when making changes to ImplicitCAD.
## "no point" releases:
### Comment / Format / Messages
These changes don't improve anything but the code quality, messages output, or build system. they can add features to the parser, but cannot remove them. they may not change the md5 of the generated STL files.
1. make sure test-implicit is all green.
2. make sure parser-bench hasn't gone all out of control.
3. make sure docgen hasn't changed it's output too much.
push to master.
### Math / Types
These releases change the math engine, but only in a direction that is provably better, and shows in our examples.
1. do all of the above.
2. check 'make examples' output. look at the times that valgrind measures.
3. check the md5sum of the .stl files output.
If the md5sums of the last release and this one differ, run admesh on both, and examine the output. if the output is conclusively better for all changed examples, then proceed to push.
push to master.
## point releases:
These releases change the quality of the output significantly enough that poking it with admesh is indeterminate, or they include changes to the parser such that old code would not work.
## major releases:

View File

@ -1,2 +1,3 @@
import Distribution.Simple
main :: IO ()
main = defaultMain

16
Tools.md Normal file
View File

@ -0,0 +1,16 @@
# Purpose of this document:
List the external tools i've found useful with this codebase.
# Tools:
## Workflow:
My workflow consists of:
### admesh
### meshlab
## Code Checking
'weeder' is useful.

View File

@ -1,88 +0,0 @@
-- Implicit CAD. Copyright (C) 2011, Christopher Olah (chris@colah.ca)
-- Released under the GNU GPL, see LICENSE
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances, FlexibleContexts, TypeSynonymInstances, UndecidableInstances, ScopedTypeVariables #-}
-- FIXME: this doesn't work. looks like it broke badly when ArgParser became a Monad.
import Graphics.Implicit.ExtOpenScad.Primitives (primitives)
import Graphics.Implicit.ExtOpenScad.Util.ArgParser
import Control.Monad
isExample (ExampleDoc _ ) = True
isExample _ = False
isArgument (ArgumentDoc _ _ _) = True
isArgument _ = False
main = do
let names = map fst primitives
docs <- sequence $ map (getArgParserDocs.($ []).snd) primitives
forM_ (zip names docs) $ \(moduleName, moduleDocList) -> do
let
examples = filter isExample moduleDocList
arguments = filter isArgument moduleDocList
putStrLn moduleName
putStrLn (map (const '-') moduleName)
putStrLn ""
if not $ null examples then putStrLn "**Examples:**\n" else return ()
forM_ examples $ \(ExampleDoc example) -> do
putStrLn $ " * `" ++ example ++ "`"
putStrLn ""
putStrLn "**Arguments:**\n"
forM_ arguments $ \(ArgumentDoc name posfallback description) ->
case (posfallback, description) of
(Nothing, "") -> do
putStrLn $ " * `" ++ name ++ "`"
(Just fallback, "") -> do
putStrLn $ " * `" ++ name ++ " = " ++ fallback ++ "`"
(Nothing, _) -> do
putStrLn $ " * `" ++ name ++ "`"
putStrLn $ " " ++ description
(Just fallback, _) -> do
putStrLn $ " * `" ++ name ++ " = " ++ fallback ++ "`"
putStrLn $ " " ++ description
putStrLn ""
-- | We need a format to extract documentation into
data Doc = Doc String [DocPart]
deriving (Show)
data DocPart = ExampleDoc String
| ArgumentDoc String (Maybe String) String
deriving (Show)
-- Here there be dragons!
-- Because we made this a Monad instead of applicative functor, there's now sane way to do this.
-- We give undefined (= an error) and let laziness prevent if from ever being touched.
-- We're using IO so that we can catch an error if this backfires.
-- If so, we *back off*.
-- | Extract Documentation from an ArgParser
getArgParserDocs ::
(ArgParser a) -- ^ ArgParser
-> IO [DocPart] -- ^ Docs (sadly IO wrapped)
getArgParserDocs (ArgParser name fallback doc fnext) =
do
otherDocs <- Ex.catch (getArgParserDocs $ fnext undefined) (\(e :: Ex.SomeException) -> return [])
return $ (ArgumentDoc name (fmap show fallback) doc):otherDocs
getArgParserDocs (ArgParserExample str child) =
do
childResults <- getArgParserDocs child
return $ (ExampleDoc str) : childResults
-- We try to look at as little as possible, to avoid the risk of triggering an error.
-- Yay laziness!
getArgParserDocs (ArgParserTest _ _ child ) = getArgParserDocs child
getArgParserDocs (ArgParserFailIf _ _ child ) = getArgParserDocs child
-- To look at this one would almost certainly be death (exception)
getArgParserDocs (ArgParserTerminator _ ) = return []

View File

@ -1,117 +1,113 @@
name: implicit
version: 0.1.0
cabal-version: >= 1.8
synopsis: Math-inspired programmatic 2&3D CAD: CSG, bevels, and shells; gcode export..
description: A math-inspired programmatic CAD library in haskell.
Name: implicit
Version: 0.2.1
Cabal-version: >= 1.8
Tested-with: GHC >= 8.2
Build-type: Simple
Synopsis: A Math-inspired programmatic 2&3D CAD system: CSG, bevels, and shells; gcode export..
Description: A math-inspired programmatic CAD library in haskell.
Build objects with constructive solid geometry, bevels,
shells and more in 2D & 3D. Then export to SVGs, STLs,
or produce gcode directly!
license: AGPL-3
license-file: LICENSE
author: Christopher Olah
maintainer: Julia Longtin <julial@turinglace.com>
homepage: http://kalli1.faikvm.com/ImplicitCAD/Stable
build-type: Simple
category: Graphics
License: AGPL-3
License-file: LICENSE
Author: Julia Longtin <julial@turinglace.com>
Maintainer: Julia Longtin <julial@turinglace.com>
Homepage: http://implicitcad.org/
Category: Graphics
library
Library
build-depends:
base >= 3 && < 5,
filepath,
directory,
download,
parsec,
unordered-containers,
parallel,
containers,
deepseq,
vector-space,
text,
monads-tf,
bytestring,
bytestring-builder,
blaze-builder,
blaze-markup,
blaze-svg,
storable-endian,
JuicyPixels,
NumInstances,
criterion,
snap-core,
snap-server,
silently,
transformers
Build-depends:
base >= 3 && < 5,
filepath,
directory,
parsec,
parallel,
containers,
deepseq,
hspec,
vector-space,
text,
monads-tf,
bytestring,
blaze-builder,
blaze-markup,
blaze-svg,
storable-endian,
JuicyPixels,
transformers
ghc-options:
-Wall
-- for debugging only.
-- -Weverything
-O2
-optc-O3
-- cannot use, we use infinity in some calculations.
-- -optc-ffast-math
Ghc-options:
-O2
-optc-O3
-- -dynamic
-- see GHC manual 8.2.1 section 6.5.1.
-feager-blackholing
-- for debugging.
-Wall
-Wextra
-Weverything
extensions:
exposed-modules:
Graphics.Implicit
Graphics.Implicit.Definitions
Graphics.Implicit.Primitives
Graphics.Implicit.Export
Graphics.Implicit.MathUtil
Graphics.Implicit.ExtOpenScad
Graphics.Implicit.ObjectUtil
-- Note that these modules are only temporarily exposed, to
-- allow coding the unit tests against the current parser
-- interface.
Graphics.Implicit.ExtOpenScad.Parser.Statement
Graphics.Implicit.ExtOpenScad.Parser.Expr
Graphics.Implicit.ExtOpenScad.Definitions
-- these are exported for Benchmark.
Graphics.Implicit.Export.SymbolicObj2
Graphics.Implicit.Export.SymbolicObj3
-- these are exported for implicitsnap.
Graphics.Implicit.Export.TriangleMeshFormats
Graphics.Implicit.Export.PolylineFormats
Graphics.Implicit.Export.DiscreteAproxable
Exposed-modules:
Graphics.Implicit
Graphics.Implicit.Definitions
Graphics.Implicit.Primitives
Graphics.Implicit.Export
Graphics.Implicit.MathUtil
Graphics.Implicit.ExtOpenScad
Graphics.Implicit.ObjectUtil
-- These modules are exposed for the unit tests against the parser interface.
Graphics.Implicit.ExtOpenScad.Parser.Statement
Graphics.Implicit.ExtOpenScad.Parser.Expr
Graphics.Implicit.ExtOpenScad.Definitions
-- These are exposed for Benchmark.
Graphics.Implicit.Export.SymbolicObj2
Graphics.Implicit.Export.SymbolicObj3
-- These are exposed for implicitsnap.
Graphics.Implicit.Export.TriangleMeshFormats
Graphics.Implicit.Export.PolylineFormats
Graphics.Implicit.Export.DiscreteAproxable
-- These are exposed for docgen.
Graphics.Implicit.ExtOpenScad.Primitives
other-modules:
Graphics.Implicit.ObjectUtil.GetBox2
Graphics.Implicit.ObjectUtil.GetBox3
Graphics.Implicit.ObjectUtil.GetImplicit2
Graphics.Implicit.ObjectUtil.GetImplicit3
Graphics.Implicit.ExtOpenScad.Default
Graphics.Implicit.ExtOpenScad.Parser.Util
Graphics.Implicit.ExtOpenScad.Primitives
Graphics.Implicit.ExtOpenScad.Eval.Statement
Graphics.Implicit.ExtOpenScad.Eval.Expr
Graphics.Implicit.ExtOpenScad.Util.StateC
Graphics.Implicit.ExtOpenScad.Util.ArgParser
Graphics.Implicit.ExtOpenScad.Util.OVal
Graphics.Implicit.Export.MarchingSquares
Graphics.Implicit.Export.MarchingSquaresFill
Graphics.Implicit.Export.RayTrace
Graphics.Implicit.Export.NormedTriangleMeshFormats
Graphics.Implicit.Export.SymbolicFormats
Graphics.Implicit.Export.Util
Graphics.Implicit.Export.TextBuilderUtils
Graphics.Implicit.Export.Symbolic.Rebound2
Graphics.Implicit.Export.Symbolic.Rebound3
Graphics.Implicit.Export.Render
Graphics.Implicit.Export.Render.Definitions
Graphics.Implicit.Export.Render.GetLoops
Graphics.Implicit.Export.Render.GetSegs
Graphics.Implicit.Export.Render.HandleSquares
Graphics.Implicit.Export.Render.Interpolate
Graphics.Implicit.Export.Render.RefineSegs
Graphics.Implicit.Export.Render.TesselateLoops
Graphics.Implicit.Export.Render.HandlePolylines
Other-modules:
Graphics.Implicit.FastIntUtil
Graphics.Implicit.IntegralUtil
Graphics.Implicit.ObjectUtil.GetBox2
Graphics.Implicit.ObjectUtil.GetBox3
Graphics.Implicit.ObjectUtil.GetImplicit2
Graphics.Implicit.ObjectUtil.GetImplicit3
Graphics.Implicit.ExtOpenScad.Default
Graphics.Implicit.ExtOpenScad.Parser.Util
Graphics.Implicit.ExtOpenScad.Eval.Statement
Graphics.Implicit.ExtOpenScad.Eval.Expr
Graphics.Implicit.ExtOpenScad.Util.ArgParser
Graphics.Implicit.ExtOpenScad.Util.OVal
Graphics.Implicit.ExtOpenScad.Util.StateC
-- Historic, but functional. Should be merged into MarchingSquaresFill.
-- Graphics.Implicit.Export.MarchingSquares
Graphics.Implicit.Export.MarchingSquaresFill
Graphics.Implicit.Export.RayTrace
Graphics.Implicit.Export.NormedTriangleMeshFormats
Graphics.Implicit.Export.SymbolicFormats
Graphics.Implicit.Export.Util
Graphics.Implicit.Export.TextBuilderUtils
Graphics.Implicit.Export.Symbolic.Rebound2
Graphics.Implicit.Export.Symbolic.Rebound3
Graphics.Implicit.Export.Render
Graphics.Implicit.Export.Render.Definitions
Graphics.Implicit.Export.Render.GetLoops
Graphics.Implicit.Export.Render.GetSegs
Graphics.Implicit.Export.Render.HandleSquares
Graphics.Implicit.Export.Render.Interpolate
Graphics.Implicit.Export.Render.RefineSegs
Graphics.Implicit.Export.Render.TesselateLoops
Graphics.Implicit.Export.Render.HandlePolylines
executable extopenscad
main-is: extopenscad.hs
hs-source-dirs: programs
build-depends:
Executable extopenscad
Main-is: extopenscad.hs
Hs-source-dirs: programs
Build-depends:
base,
containers,
vector-space,
@ -119,129 +115,114 @@ executable extopenscad
parallel,
optparse-applicative >= 0.10.0,
implicit
ghc-options:
-threaded
-rtsopts
-Wall
-O2
-optc-O3
-optc-ffast-math
Ghc-options:
-O2
-optc-O3
-threaded
-rtsopts
-- -dynamic
-- see GHC manual 8.2.1 section 6.5.1.
-feager-blackholing
-- for debugging.
-Wall
-Wextra
-Weverything
-- FIXME: does not compile.
--Executable docgen
-- main-is: docgen.hs
-- build-depends:
-- base,
-- vector-space,
-- text,
-- JuicyPixels,
-- blaze-builder,
-- blaze-svg,
-- blaze-markup,
-- parallel,
-- deepseq,
-- vector-space,
-- monads-tf,
-- bytestring,
-- storable-endian,
-- parsec,
-- directory,
-- containers,
-- filepath,
-- snap-core,
-- snap-server,
-- silently,
-- transformers
-- ghc-options:
-- -optc-O3
-- -threaded
-- -rtsopts
-- -funfolding-use-threshold=16
-- -fspec-constr-count=10
executable implicitsnap
main-is: implicitsnap.hs
hs-source-dirs: programs
Executable docgen
main-is: docgen.hs
Hs-source-dirs: programs
build-depends:
base,
vector-space,
text,
JuicyPixels,
blaze-builder,
blaze-svg,
blaze-markup,
parallel,
deepseq,
vector-space,
monads-tf,
implicit
ghc-options:
-- -dynamic
-- for debugging.
-Wall
-Wextra
-Weverything
Executable implicitsnap
Main-is: implicitsnap.hs
Hs-source-dirs: programs
Build-depends:
base,
bytestring,
storable-endian,
parsec,
directory,
containers,
filepath,
implicit,
parallel,
parsec,
silently,
snap-core,
snap-server,
silently,
transformers,
implicit
ghc-options:
-threaded
-rtsopts
-Wall
-O2
-optc-O3
-optc-ffast-math
executable Benchmark
main-is: Benchmark.hs
hs-source-dirs: programs
build-depends:
base,
text,
JuicyPixels,
blaze-svg,
blaze-markup,
parallel,
deepseq,
vector-space,
monads-tf,
blaze-builder,
bytestring,
storable-endian,
parsec,
directory,
containers,
filepath,
vector-space
Ghc-options:
-threaded
-rtsopts
-O2
-optc-O3
-dynamic
-- see GHC manual 8.2.1 section 6.5.1.
-feager-blackholing
-- for debugging.
-Wall
-Wextra
-Weverything
Executable Benchmark
Main-is: Benchmark.hs
Hs-source-dirs: programs
Build-depends:
base,
criterion,
transformers,
implicit
ghc-options:
-threaded
-rtsopts
-Wall
-O2
-optc-O3
-optc-ffast-math
Ghc-options:
-O2
-optc-O3
-- -dynamic
-- for debugging.
-Wall
-Wextra
-Weverything
test-suite test-implicit
type: exitcode-stdio-1.0
build-depends: base, mtl, containers, hspec, parsec, implicit
main-is: Main.hs
hs-source-dirs: tests
Test-suite test-implicit
Type: exitcode-stdio-1.0
Build-depends:
base,
containers,
hspec,
implicit,
mtl,
parsec
Main-is: Main.hs
Hs-source-dirs: tests
Ghc-options:
-O2
-optc-O3
-- -dynamic
-- for debugging.
-Wall
-Wextra
-Weverything
Other-Modules:
ParserSpec.Expr
ParserSpec.Statement
ParserSpec.Util
benchmark parser-bench
type: exitcode-stdio-1.0
hs-source-dirs: bench
main-is: ParserBench.hs
build-depends: base, criterion, random, parsec, implicit
ghc-options:
-Wall
-O2 -optc-O3
Benchmark parser-bench
Type: exitcode-stdio-1.0
Build-depends: base, criterion, random, parsec, implicit
Main-is: parser-bench.hs
Hs-source-dirs: programs
Ghc-options:
-O2
-optc-O3
-- -dynamic
-- for debugging.
-Wall
-Wextra
-Weverything
source-repository head
type: git
location: https://github.com/colah/ImplicitCAD.git
Source-repository head
Type: git
Location: https://github.com/colah/ImplicitCAD.git

View File

@ -2,26 +2,31 @@
-- Copyright (C) 2014 2015 2016, Julia Longtin (julial@turinglace.com)
-- Released under the GNU AGPLV3+, see LICENSE
-- Benchmarks
-- Our benchmarking suite.
-- Let's be explicit about where things come from :)
-- Use criterion for benchmarking. see <http://www.serpentine.com/criterion/>
import Criterion.Main
import Prelude (($), (*), (/), String, IO, cos, pi, map, zip3, Maybe(Just, Nothing), Either(Left), fromIntegral, (++))
-- The parts of ImplicitCAD we know how to benchmark (in theory).
import Graphics.Implicit (union, circle, writeSVG, writePNG2, writePNG3, writeSTL, SymbolicObj2, SymbolicObj3)
-- Use criterion for benchmarking. see <http://www.serpentine.com/criterion/>
import Criterion.Main (Benchmark, bgroup, bench, nf, nfAppIO, defaultMain)
-- The parts of ImplicitCAD we know how to benchmark.
import Graphics.Implicit (union, circle, sphere, SymbolicObj2, SymbolicObj3, writeDXF2, writeSVG, writePNG2, writeSTL, writeBinSTL, unionR, translate, difference, extrudeRM, rect3R)
import Graphics.Implicit.Export.SymbolicObj2 (symbolicGetContour)
import Graphics.Implicit.Export.SymbolicObj3 (symbolicGetMesh)
import Graphics.Implicit.Primitives (translate, difference, extrudeRM, rect3R)
-- The variables defining distance and counting in our world.
import Graphics.Implicit.Definitions (, Fast)
-- Haskell representations of objects to benchmark.
-- FIXME: move each of these objects into seperate compilable files.
-- FIXME: move each of these objects into seperate compilable files.
-- | What we extrude in the example on the website.
obj2d_1 :: SymbolicObj2
obj2d_1 =
union
unionR 8
[ circle 10
, translate (22,0) $ circle 10
, translate (0,22) $ circle 10
@ -29,21 +34,28 @@ obj2d_1 =
, translate (0,-22) $ circle 10
]
-- | An extruded version of obj2d_1, should be identical to the website's example, and example5.escad.
object1 :: SymbolicObj3
object1 = extrudeRM 0 (Just twist) Nothing Nothing obj2d_1 (Left 40)
where twist h = 35*cos(h*2*pi/60)
where
twist :: ->
twist h = 35*cos(h*2*pi/60)
-- | another 3D object, for benchmarking.
object2 :: SymbolicObj3
object2 = squarePipe (10,10,10) 1 100
where squarePipe (x,y,z) diameter precision =
where
squarePipe :: (,,) -> -> -> SymbolicObj3
squarePipe (x,y,z) diameter precision =
union
$ map (\start-> translate start
$ rect3R 0 (0,0,0) (diameter,diameter,diameter)
)
$ zip3 (map (\n->(n/precision)*x) [0..precision])
(map (\n->(n/precision)*y) [0..precision])
(map (\n->(n/precision)*z) [0..precision])
$ zip3 (map (\n->((fromIntegral n)/precision)*x) [0..100::Fast])
(map (\n->((fromIntegral n)/precision)*y) [0..100::Fast])
(map (\n->((fromIntegral n)/precision)*z) [0..100::Fast])
-- | A third 3d object to benchmark.
object3 :: SymbolicObj3
object3 =
difference
@ -51,31 +63,45 @@ object3 =
, rect3R 1 (0,0,0) (2,2,2)
]
obj2Benchmarks :: String -> SymbolicObj2 -> Benchmark
obj2Benchmarks name obj =
-- | Example 13 - the rounded union of a cube and a sphere.
object4 :: SymbolicObj3
object4 = union [
rect3R 0 (0,0,0) (20,20,20),
translate (20,20,20) (sphere 15) ]
-- | Benchmark a 2D object.
obj2Benchmarks :: String -> String -> SymbolicObj2 -> Benchmark
obj2Benchmarks name filename obj =
bgroup name
[
-- bench "SVG write" $ writeSVG 1 "benchmark.svg" obj
-- , bench "PNG write" $ writePNG2 1 "benchmark.png" obj
-- ,
bench "Get contour" $ nf (symbolicGetContour 1) obj
bench "SVG write" $ nfAppIO (writeSVG 1 $ filename ++ ".svg") obj,
bench "PNG write" $ nfAppIO (writePNG2 1 $ filename ++ ".png") obj,
bench "DXF write" $ nfAppIO (writeDXF2 1 $ filename ++ ".dxf") obj,
bench "Get contour" $ nf (symbolicGetContour 1) obj
]
obj3Benchmarks :: String -> SymbolicObj3 -> Benchmark
obj3Benchmarks name obj =
-- | Benchmark a 3D object.
obj3Benchmarks :: String -> String -> SymbolicObj3 -> Benchmark
obj3Benchmarks name filename obj =
bgroup name
[
-- bench "PNG write" $ writePNG3 1 "benchmark.png" obj
-- , bench "STL write" $ writeSTL 1 "benchmark.stl" obj
-- ,
bench "STLTEXT write" $ nfAppIO (writeSTL 1 $ filename ++ ".stl.text") obj,
bench "STL write" $ nfAppIO (writeBinSTL 1 $ filename ++ ".stl") obj,
bench "Get mesh" $ nf (symbolicGetMesh 1) obj
]
-- | Benchmark all of our objects.
benchmarks :: [Benchmark]
benchmarks =
[ obj3Benchmarks "Object 1" object1
, obj3Benchmarks "Object 2" object2
, obj3Benchmarks "Object 3" object3
[ obj3Benchmarks "Object 1" "example5" object1
, obj3Benchmarks "Object 2" "object2" object2
, obj3Benchmarks "Object 3" "object3" object3
, obj3Benchmarks "Object 4" "object4" object4
, obj2Benchmarks "Object 2d 1" "example18" obj2d_1
]
-- | Our entrypoint. Runs all benchmarks.
main :: IO ()
main = defaultMain benchmarks

172
programs/docgen.hs Normal file
View File

@ -0,0 +1,172 @@
-- Implicit CAD. Copyright (C) 2011, Christopher Olah (chris@colah.ca)
-- Released under the GNU GPL, see LICENSE
-- FIXME: document why we need each of these.
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ScopedTypeVariables #-}
import Prelude(IO, Show, String, Int, Maybe(Just,Nothing), Eq, return, ($), show, fmap, (++), putStrLn, filter, zip, null, map, undefined, const, Bool(True,False), fst, snd, sequence, (.), concat, head, tail, sequence, length, (>), (/=), (+))
import Graphics.Implicit.ExtOpenScad.Primitives (primitives)
import Graphics.Implicit.ExtOpenScad.Definitions (ArgParser(AP,APFailIf,APExample,APTest,APTerminator,APBranch))
import qualified Control.Exception as Ex (catch, SomeException)
import Control.Monad (forM_, mapM)
-- | Return true if the argument is of type ExampleDoc.
isExample (ExampleDoc _ ) = True
isExample _ = False
-- | Return true if the argument is of type ArgumentDoc.
isArgument (ArgumentDoc _ _ _) = True
isArgument _ = False
-- | Return true if the argument is of type Branch.
isBranch (Branch _) = True
isBranch _ = False
dumpPrimitive :: String -> [DocPart] -> Int -> IO ()
dumpPrimitive moduleName moduleDocList level = do
let
examples = filter isExample moduleDocList
arguments = filter isArgument moduleDocList
syntaxes = filter isBranch moduleDocList
moduleLabel = moduleName
if level /= 0
then
do
putStrLn $ "#" ++ moduleLabel
else
do
putStrLn moduleLabel
putStrLn (map (const '-') moduleLabel)
putStrLn ""
if null examples
then
return ()
else
do
putStrLn "#Examples:\n"
forM_ examples $ \(ExampleDoc example) -> do
putStrLn $ " * `" ++ example ++ "`"
putStrLn ""
if null arguments
then
return ()
else
do
if level /= 0
then
putStrLn "##Arguments:\n"
else
if null syntaxes
then
putStrLn "#Arguments:\n"
else
putStrLn "#Shared Arguments:\n"
forM_ arguments $ \(ArgumentDoc name posfallback description) ->
case (posfallback, description) of
(Nothing, "") -> do
putStrLn $ " * `" ++ name ++ "`"
(Just fallback, "") -> do
putStrLn $ " * `" ++ name ++ " = " ++ fallback ++ "`"
(Nothing, _) -> do
putStrLn $ " * `" ++ name ++ "`"
putStrLn $ " " ++ description
(Just fallback, _) -> do
putStrLn $ " * `" ++ name ++ " = " ++ fallback ++ "`"
putStrLn $ " " ++ description
putStrLn ""
if null syntaxes
then
return ()
else
forM_ syntaxes $ \(Branch syntax) -> do
dumpPrimitive ("Syntax " ++ (show $ level+1)) syntax (level+1)
-- | Our entrypoint. Generate one document describing all of our primitives.
main :: IO ()
main = do
docs <- mapM (getArgParserDocs.($ []).snd) primitives
let
names = map fst primitives
docname = "ImplicitCAD Primitives"
putStrLn (map (const '=') docname)
putStrLn docname
putStrLn (map (const '=') docname)
putStrLn ""
putStrLn ""
forM_ (zip names docs) $ \(moduleName, moduleDocList) -> do
dumpPrimitive moduleName moduleDocList 0
-- | the format we extract documentation into
data Doc = Doc String [DocPart]
deriving (Show)
data DocPart = ExampleDoc String
| ArgumentDoc String (Maybe String) String
| Branch [DocPart]
| Empty
deriving (Show, Eq)
-- Here there be dragons!
-- Because we made this a Monad instead of applicative functor, there's no sane way to do this.
-- We give undefined (= an error) and let laziness prevent if from ever being touched.
-- We're using IO so that we can catch an error if this backfires.
-- If so, we *back off*.
-- | Extract Documentation from an ArgParser
getArgParserDocs ::
(ArgParser a) -- ^ ArgParser(s)
-> IO [DocPart] -- ^ Docs (sadly IO wrapped)
getArgParserDocs (AP name fallback doc fnext) = do
otherDocs <- Ex.catch (getArgParserDocs $ fnext undefined) (\(e :: Ex.SomeException) -> return [])
if (otherDocs /= [Empty])
then
do
return $ [(ArgumentDoc name (fmap show fallback) doc)] ++ (otherDocs)
else
do
return $ [(ArgumentDoc name (fmap show fallback) doc)]
getArgParserDocs (APFailIf _ _ child) = do
childResults <- getArgParserDocs child
return $ childResults
getArgParserDocs (APExample str child) = do
childResults <- getArgParserDocs child
return $ (ExampleDoc str):(childResults)
-- We try to look at as little as possible, to avoid the risk of triggering an error.
-- Yay laziness!
getArgParserDocs (APTest _ _ child) = do
childResults <- getArgParserDocs child
return $ childResults
-- To look at this one would almost certainly be death (exception)
getArgParserDocs (APTerminator _) = return $ [(Empty)]
-- This one confuses me.
getArgParserDocs (APBranch children) = do
putStrLn $ show $ length children
otherDocs <- Ex.catch (getArgParserDocs (APBranch $ tail children)) (\(e :: Ex.SomeException) -> return [])
aResults <- getArgParserDocs $ head children
if (otherDocs /= [(Empty)])
then
do
return $ [Branch ((aResults)++(otherDocs))]
else
do
return aResults

View File

@ -3,16 +3,18 @@
-- Copyright (C) 2014 2016, Mike MacHenry (mike.machenry@gmail.com)
-- Released under the GNU GPL, see LICENSE
-- FIXME: add support for AMF.
-- An interpreter to run extended OpenScad code, outputing STL, OBJ, SVG, SCAD, PNG, or GCODE.
-- An interpreter to run extended OpenScad code. outputs STL, OBJ, SVG, SCAD, PNG, DXF, or GCODE.
-- Enable additional syntax to make our code more readable.
{-# LANGUAGE ViewPatterns , PatternGuards #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE PatternGuards #-}
-- Let's be explicit about what we're getting from where :)
import Prelude (Read(readsPrec), Maybe(Just, Nothing), Either(Left, Right), IO, FilePath, Show, Eq, String, (++), ($), (*), (/), (==), (>), (**), (-), readFile, minimum, drop, error, map, fst, min, sqrt, tail, take, length, putStrLn, show, print, (>>=), lookup)
-- Our Extended OpenScad interpreter, and functions to write out files in designated formats.
import Graphics.Implicit (runOpenscad, writeSVG, writeBinSTL, writeOBJ, writeSCAD2, writeSCAD3, writeGCodeHacklabLaser, writePNG2, writePNG3)
import Graphics.Implicit (runOpenscad, writeSVG, writeDXF2, writeBinSTL, writeOBJ, writeSCAD2, writeSCAD3, writeGCodeHacklabLaser, writePNG2, writePNG3)
-- Functions for finding a box around an object, so we can define the area we need to raytrace inside of.
import Graphics.Implicit.ObjectUtil (getBox2, getBox3)
@ -37,7 +39,7 @@ import Graphics.Implicit.ExtOpenScad.Definitions (OVal (ONum))
-- Operator to subtract two points. Used when defining the resolution of a 2d object.
import Data.AffineSpace ((.-.))
import Data.Monoid (Monoid, mappend, mconcat)
import Data.Monoid (Monoid, mappend)
import Control.Applicative ((<$>), (<*>))
@ -47,13 +49,13 @@ import Options.Applicative (fullDesc, progDesc, header, auto, info, helper, help
-- For handling input/output files.
import System.FilePath (splitExtension)
-- The following is needed to ensure backwards/forwards compatibility
-- Backwards compatibility with old versions of Data.Monoid:
-- | The following is needed to ensure backwards/forwards compatibility
-- | with old versions of Data.Monoid:
infixr 6 <>
(<>) :: Monoid a => a -> a -> a
(<>) = mappend
-- A datatype for containing our command line options.
-- | Our command line options.
data ExtOpenScadOpts = ExtOpenScadOpts
{ outputFile :: Maybe FilePath
, outputFormat :: Maybe OutputFormat
@ -61,7 +63,7 @@ data ExtOpenScadOpts = ExtOpenScadOpts
, inputFile :: FilePath
}
-- A datatype enumerating our output file formats types.
-- | A type serving to enumerate our output formats.
data OutputFormat
= SVG
| SCAD
@ -69,10 +71,11 @@ data OutputFormat
| GCode
| STL
| OBJ
-- | AMF
deriving (Show, Eq, Ord)
-- | 3MF
| DXF
deriving (Show, Eq)
-- A list mapping file extensions to output formats.
-- | A list mapping file extensions to output formats.
formatExtensions :: [(String, OutputFormat)]
formatExtensions =
[ ("svg", SVG)
@ -82,18 +85,19 @@ formatExtensions =
, ("gcode", GCode)
, ("stl", STL)
, ("obj", OBJ)
-- , ("amf", AMF)
-- , ("3mf", 3MF)
, ("dxf", DXF)
]
-- Lookup an output format for a given output file. Throw an error if one cannot be found.
-- | Lookup an output format for a given output file. Throw an error if one cannot be found.
guessOutputFormat :: FilePath -> OutputFormat
guessOutputFormat fileName =
maybe (error $ "Unrecognized output format: "<>ext) id
fromMaybe (error $ "Unrecognized output format: " <> ext)
$ readOutputFormat $ tail ext
where
(_,ext) = splitExtension fileName
-- The parser for our command line arguments.
-- | The parser for our command line arguments.
extOpenScadOpts :: Parser ExtOpenScadOpts
extOpenScadOpts = ExtOpenScadOpts
<$> optional (
@ -125,26 +129,28 @@ extOpenScadOpts = ExtOpenScadOpts
<> help "Input extended OpenSCAD file"
)
-- Try to look up an output format from a supplied extension.
-- | Try to look up an output format from a supplied extension.
readOutputFormat :: String -> Maybe OutputFormat
readOutputFormat ext = lookup (map toLower ext) formatExtensions
-- A Read instance for our output format. Used by 'auto' in our command line parser.
-- Reads a string, and evaluates to the appropriate OutputFormat.
-- | A Read instance for our output format. Used by 'auto' in our command line parser.
-- Reads a string, and evaluates to the appropriate OutputFormat.
instance Read OutputFormat where
readsPrec _ myvalue =
tryParse formatExtensions
where tryParse [] = [] -- If there is nothing left to try, fail
tryParse ((attempt, result):xs) =
if (take (length attempt) myvalue) == attempt
then [(result, drop (length attempt) myvalue)]
else tryParse xs
where
tryParse :: [(String, OutputFormat)] -> [(OutputFormat, String)]
tryParse [] = [] -- If there is nothing left to try, fail
tryParse ((attempt, result):xs) =
if take (length attempt) myvalue == attempt
then [(result, drop (length attempt) myvalue)]
else tryParse xs
-- Find the resolution to raytrace at.
getRes :: (Map.Map [Char] OVal, [SymbolicObj2], [SymbolicObj3]) ->
-- First, use a resolution specified by a variable in the input file.
-- | Find the resolution to raytrace at.
getRes :: (Map.Map String OVal, [SymbolicObj2], [SymbolicObj3]) ->
-- | First, use a resolution specified by a variable in the input file.
getRes (Map.lookup "$res" -> Just (ONum res), _, _) = res
-- Use a resolution chosen for 3D objects.
-- | Use a resolution chosen for 3D objects.
-- FIXME: magic numbers.
getRes (varlookup, _, obj:_) =
let
@ -153,19 +159,19 @@ getRes (varlookup, _, obj:_) =
in case fromMaybe (ONum 1) $ Map.lookup "$quality" varlookup of
ONum qual | qual > 0 -> min (minimum [x,y,z]/2) ((x*y*z/qual)**(1/3) / 22)
_ -> min (minimum [x,y,z]/2) ((x*y*z)**(1/3) / 22)
-- Use a resolution chosen for 2D objects.
-- | Use a resolution chosen for 2D objects.
-- FIXME: magic numbers.
getRes (varlookup, obj:_, _) =
let
(p1,p2) = getBox2 obj
(x,y) = p2 .-. p1
in case fromMaybe (ONum 1) $ Map.lookup "$quality" varlookup of
ONum qual | qual > 0 -> min (min x y/2) ((x*y/qual)**0.5 / 30)
_ -> min (min x y/2) ((x*y)**0.5 / 30)
-- fallthrough value.
ONum qual | qual > 0 -> min (min x y/2) (sqrt(x*y/qual) / 30)
_ -> min (min x y/2) (sqrt(x*y) / 30)
-- | fallthrough value.
getRes _ = 1
-- Output a file containing a 3D object.
-- | Output a file containing a 3D object.
export3 :: Maybe OutputFormat -> -> FilePath -> SymbolicObj3 -> IO ()
export3 posFmt res output obj =
case posFmt of
@ -176,36 +182,37 @@ export3 posFmt res output obj =
Nothing -> writeBinSTL res output obj
Just fmt -> putStrLn $ "Unrecognized 3D format: "<>show fmt
-- Output a file containing a 2D object.
-- | Output a file containing a 2D object.
export2 :: Maybe OutputFormat -> -> FilePath -> SymbolicObj2 -> IO ()
export2 posFmt res output obj =
case posFmt of
Just SVG -> writeSVG res output obj
Just DXF -> writeDXF2 res output obj
Just SCAD -> writeSCAD2 res output obj
Just PNG -> writePNG2 res output obj
Just GCode -> writeGCodeHacklabLaser res output obj
Nothing -> writeSVG res output obj
Just fmt -> putStrLn $ "Unrecognized 2D format: "<>show fmt
-- Interpret arguments, and render the object defined in the supplied input file.
run :: ExtOpenScadOpts -> IO()
-- | Interpret arguments, and render the object defined in the supplied input file.
run :: ExtOpenScadOpts -> IO ()
run args = do
putStrLn $ "Loading File."
putStrLn "Loading File."
content <- readFile (inputFile args)
let format =
case () of
_ | Just fmt <- outputFormat args -> Just $ fmt
_ | Just fmt <- outputFormat args -> Just fmt
_ | Just file <- outputFile args -> Just $ guessOutputFormat file
_ -> Nothing
putStrLn $ "Processing File."
putStrLn "Processing File."
case runOpenscad content of
Left err -> putStrLn $ show $ err
Left err -> print err
Right openscadProgram -> do
s@(_, obj2s, obj3s) <- openscadProgram
let res = maybe (getRes s) id (resolution args)
let res = fromMaybe (getRes s) (resolution args)
let basename = fst (splitExtension $ inputFile args)
let posDefExt = case format of
Just f -> Prelude.lookup f (map swap formatExtensions)
@ -218,7 +225,7 @@ run args = do
putStrLn $ "Rendering 3D object to " ++ output
putStrLn $ "With resolution " ++ show res
putStrLn $ "In box " ++ show (getBox3 obj)
putStrLn $ show obj
print obj
export3 format res output obj
([obj], []) -> do
let output = fromMaybe
@ -227,17 +234,17 @@ run args = do
putStrLn $ "Rendering 2D object to " ++ output
putStrLn $ "With resolution " ++ show res
putStrLn $ "In box " ++ show (getBox2 obj)
putStrLn $ show obj
print obj
export2 format res output obj
([], []) -> putStrLn "No objects to render."
_ -> putStrLn "Multiple/No objects, what do you want to render?"
_ -> putStrLn "A mixture of 2D and 3D objects, what do you want to render?"
-- The entry point. Use the option parser then run the extended OpenScad code.
main :: IO()
-- | The entry point. Use the option parser then run the extended OpenScad code.
main :: IO ()
main = execParser opts >>= run
where
opts= info (helper <*> extOpenScadOpts)
( fullDesc
<> progDesc "ImplicitCAD: Extended OpenSCAD interpreter."
<> progDesc "ImplicitCAD: Extended OpenSCAD interpreter."
<> header "extopenscad - Extended OpenSCAD"
)

View File

@ -5,31 +5,44 @@
-- Allow us to use explicit foralls when writing function type declarations.
{-# LANGUAGE ExplicitForAll #-}
{-# LANGUAGE OverloadedStrings, ViewPatterns #-}
-- FIXME: what are these for?
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ViewPatterns #-}
-- A Snap(HTTP) server providing an ImplicitCAD REST API.
-- FIXME: we need AuthN/AuthZ for https://github.com/kliment/explicitcad to be useful.
-- Let's be explicit about what we're getting from where :)
import Prelude (IO, Maybe(Just, Nothing), Ord, String, Bool(True, False), Either(Left, Right), Show, ($), (++), (>), (.), (-), (/), (*), (**), sqrt, min, max, minimum, maximum, show, return)
import Control.Applicative ((<|>))
import Snap.Core (Snap, route, writeBS, method, Method(GET), modifyResponse, setContentType, getRequest, rqParam)
import Snap.Core (Snap, route, writeBS, method, Method(GET), modifyResponse, setContentType, setTimeout, getRequest, rqParam)
import Snap.Http.Server (quickHttpServe)
import Snap.Util.GZip (withCompression)
-- Our Extended OpenScad interpreter, and the extrudeR function for making 2D objects 3D.
import Graphics.Implicit (runOpenscad, extrudeR)
import Graphics.Implicit.ExtOpenScad.Definitions (OVal (ONum))
import Graphics.Implicit.ExtOpenScad.Definitions (OVal (ONum), VarLookup)
-- Functions for finding a box around an object, so we can define the area we need to raytrace inside of.
import Graphics.Implicit.ObjectUtil (getBox2, getBox3)
-- Definitions of the datatypes used for 2D objects, 3D objects, and for defining the resolution to raytrace at.
import Graphics.Implicit.Definitions (SymbolicObj2, SymbolicObj3, )
-- Use default values when a Maybe is Nothing.
import Data.Maybe (fromMaybe)
import Graphics.Implicit.Export.TriangleMeshFormats (jsTHREE, stl)
import Graphics.Implicit.Export.PolylineFormats (svg, hacklabLaserGCode)
-- Operator to subtract two points. Used when defining the resolution of a 2d object.
import Data.AffineSpace ((.-.))
-- class DiscreteApprox
import Graphics.Implicit.Export.DiscreteAproxable (discreteAprox)
@ -45,33 +58,62 @@ import System.IO.Silently (capture)
import qualified Data.ByteString.Char8 as BS.Char (pack, unpack)
import qualified Data.Text.Lazy as TL (unpack)
-- | The entry point. uses snap to serve a website.
main :: IO ()
main = quickHttpServe site
-- | Our site definition. Renders requests to "render/", discards all else.
site :: Snap ()
site = route
[
("render/", renderHandler)
] <|> writeBS "fall through"
-- | Our render/ handler. Uses source, callback, and opitional format to render an object.
renderHandler :: Snap ()
renderHandler = method GET $ withCompression $ do
modifyResponse $ setContentType "application/x-javascript"
setTimeout 600
request <- getRequest
case (rqParam "source" request, rqParam "callback" request, rqParam "format" request) of
(Just [source], Just [callback], Nothing) -> do
writeBS $ BS.Char.pack $ executeAndExport
(Just [source], Just [callback], Nothing) ->
writeBS . BS.Char.pack $ executeAndExport
(BS.Char.unpack source)
(BS.Char.unpack callback)
Nothing
(Just [source], Just [callback], Just [format]) -> do
writeBS $ BS.Char.pack $ executeAndExport
(Just [source], Just [callback], Just [format]) ->
writeBS . BS.Char.pack $ executeAndExport
(BS.Char.unpack source)
(BS.Char.unpack callback)
(Just $ BS.Char.unpack format)
(_, _, _) -> writeBS "must provide source and callback as 1 GET variable each"
-- | Find the resolution to raytrace at.
getRes :: forall k. (Data.String.IsString k, Ord k) => (Map k OVal, [SymbolicObj2], [SymbolicObj3]) ->
-- | If a resolution was specified in the input file, just use it.
getRes (Map.lookup "$res" -> Just (ONum res), _, _) = res
-- | If there was no resolution specified, use a resolution chosen for 3D objects.
-- FIXME: magic numbers.
getRes (varlookup, _, obj:_) =
let
((x1,y1,z1),(x2,y2,z2)) = getBox3 obj
(x,y,z) = (x2-x1, y2-y1, z2-z1)
in case fromMaybe (ONum 1) $ Map.lookup "$quality" varlookup of
ONum qual | qual > 0 -> min (minimum [x,y,z]/2) ((x*y*z/qual)**(1/3) / 22)
_ -> min (minimum [x,y,z]/2) ((x*y*z )**(1/3) / 22)
-- | ... Or use a resolution chosen for 2D objects.
-- FIXME: magic numbers.
getRes (varlookup, obj:_, _) =
let
(p1,p2) = getBox2 obj
(x,y) = p2 .-. p1
in case fromMaybe (ONum 1) $ Map.lookup "$quality" varlookup of
ONum qual | qual > 0 -> min ((min x y)/2) (sqrt(x*y/qual) / 30)
_ -> min ((min x y)/2) (sqrt(x*y ) / 30)
-- | fallthrough value.
getRes _ = 1
{-
getRes (varlookup, obj2s, obj3s) =
let
qual = case Map.lookup "$quality" varlookup of
@ -83,8 +125,8 @@ getRes (varlookup, obj2s, obj3s) =
where
((x1,y1,z1),(x2,y2,z2)) = getBox3 obj
(x,y,z) = (x2-x1, y2-y1, z2-z1)
(obj:_, _) -> ( min (min x y/2) ((x*y )**0.5 / 30)
, min (min x y/2) ((x*y/qual)**0.5 / 30) )
(obj:_, _) -> ( min (min x y/2) (sqrt(x*y ) / 30)
, min (min x y/2) (sqrt(x*y/qual) / 30) )
where
((x1,y1),(x2,y2)) = getBox2 obj
(x,y) = (x2-x1, y2-y1)
@ -98,20 +140,23 @@ getRes (varlookup, obj2s, obj3s) =
if qual <= 30
then qualRes
else -1
-}
getWidth :: forall t. (t, [SymbolicObj2], [SymbolicObj3]) ->
-- | get the maximum dimension of the object being rendered.
-- FIXME: shouldn't this get the diagonal across the box?
getWidth :: (VarLookup, [SymbolicObj2], [SymbolicObj3]) ->
getWidth (_, _, obj:_) = maximum [x2-x1, y2-y1, z2-z1]
where ((x1,y1,z1),(x2,y2,z2)) = getBox3 obj
getWidth (_, obj:_, _) = max (x2-x1) (y2-y1)
where ((x1,y1),(x2,y2)) = getBox2 obj
getWidth (_, [], []) = 0
getWidth (_, [], []) = 0
-- | Give an openscad object to run and the basename of
-- the target to write to... write an object!
executeAndExport :: String -> String -> Maybe String -> String
executeAndExport content callback maybeFormat =
let
showB :: IsString t => Bool -> t
showB True = "true"
showB False = "false"
callbackF :: Bool -> Bool -> -> String -> String
@ -119,6 +164,7 @@ executeAndExport content callback maybeFormat =
callback ++ "([null," ++ show msg ++ "," ++ showB is2D ++ "," ++ show w ++ "]);"
callbackF True is2D w msg =
callback ++ "([new Shape()," ++ show msg ++ "," ++ showB is2D ++ "," ++ show w ++ "]);"
callbackS :: (Show a1, Show a) => a -> a1 -> String
callbackS str msg = callback ++ "([" ++ show str ++ "," ++ show msg ++ ",null,null]);"
in case runOpenscad content of
Left err ->
@ -130,7 +176,7 @@ executeAndExport content callback maybeFormat =
msgs = showErrorMessages' $ errorMessages err
in callbackF False False 1 $ (\s-> "error (" ++ show line ++ "):" ++ s) msgs
Right openscadProgram -> unsafePerformIO $ do
(msgs,s) <- capture $ openscadProgram
(msgs,s) <- capture openscadProgram
let
res = getRes s
w = getWidth s
@ -162,6 +208,7 @@ executeAndExport content callback maybeFormat =
callbackS (TL.unpack (svg (discreteAprox res obj))) msgs
(Right (Just obj, _), Just "gcode/hacklab-laser") ->
callbackS (TL.unpack (hacklabLaserGCode (discreteAprox res obj))) msgs
(Right (_ , _), _) ->
callbackF False False 1 "unexpected case"

View File

@ -1,9 +1,10 @@
import Criterion.Main
import Graphics.Implicit.ExtOpenScad.Definitions
import Graphics.Implicit.ExtOpenScad.Parser.Expr
import Graphics.Implicit.ExtOpenScad.Parser.Statement
import Text.ParserCombinators.Parsec hiding (State)
import Text.Printf
import Prelude (IO, String, Int, Either(Left, Right), return, show, ($), otherwise, (==), (-), (++), concat, error)
import Criterion.Main (Benchmark, bgroup, defaultMain, bench, env, whnf)
import Graphics.Implicit.ExtOpenScad.Definitions (Expr, StatementI)
import Graphics.Implicit.ExtOpenScad.Parser.Expr (expr0)
import Graphics.Implicit.ExtOpenScad.Parser.Statement (parseProgram)
import Text.ParserCombinators.Parsec (parse)
import Text.Printf (printf)
lineComment :: Int -> String
lineComment width = "//" ++ ['x' | _ <- [1..width]] ++ "\n"
@ -24,7 +25,7 @@ assignments :: Int -> String
assignments n = concat ["x = (foo + bar);\n" | _ <- [1..n]]
intList :: Int -> String
intList n = "[" ++ concat [(show i) ++ "," | i <- [1..n]] ++ "0]"
intList n = "[" ++ concat [show i ++ "," | i <- [1..n]] ++ "0]"
parseExpr :: String -> Expr
parseExpr s = case parse expr0 "src" s of
@ -32,7 +33,7 @@ parseExpr s = case parse expr0 "src" s of
Right e -> e
parseStatements :: String -> [StatementI]
parseStatements s = case parseProgram "src" s of
parseStatements s = case parseProgram s of
Left err -> error (show err)
Right e -> e
@ -45,12 +46,12 @@ deepArithmetic n
run :: String -> (String -> a) -> String -> Benchmark
run name func input =
env (return $ input) $ \s ->
env (return input) $ \s ->
bench name $ whnf func s
main :: IO ()
main =
defaultMain $
defaultMain
[ bgroup "comments"
[ run "line" parseStatements (lineComments 5000)
, run "block" parseStatements (blockComments 10 500)

View File

@ -1,14 +1,16 @@
# For more information, see: https://github.com/commercialhaskell/stack/blob/master/doc/yaml_configuration.md
# Specifies the GHC version and set of packages available (e.g., lts-3.5, nightly-2015-09-21, ghc-7.10.2)
resolver: lts-8.18
resolver: lts-13.12
# Local packages, usually specified by relative directory name
packages:
- '.'
# Packages to be pulled from upstream that are not in the resolver (e.g., acme-missiles-0.3)
extra-deps: []
extra-deps:
- storable-endian-0.2.6
# Override default flag values for local packages and extra-deps
flags: {}

View File

@ -1,8 +1,22 @@
import Test.Hspec
import ParserSpec.Statement
import ParserSpec.Expr
-- Implicit CAD. Copyright (C) 2011, Christopher Olah (chris@colah.ca)
-- Copyright (C) 2018, Julia Longtin (julial@turinglace.com)
-- Released under the GNU AGPLV3+, see LICENSE
-- be explicit about what we import.
import Prelude (($), IO)
-- our testing engine.
import Test.Hspec(hspec, describe)
-- the test forstatements.
import ParserSpec.Statement(statementSpec)
-- the test for expressions.
import ParserSpec.Expr(exprSpec)
main :: IO ()
main = hspec $ do
describe "expressions" $ exprSpec
describe "statements" $ statementSpec
-- run tests against the expression engine.
describe "expressions" exprSpec
-- and now, against the statement engine.
describe "statements" statementSpec

View File

@ -1,103 +1,174 @@
-- Implicit CAD. Copyright (C) 2011, Christopher Olah (chris@colah.ca)
-- Copyright (C) 2014-2017, Julia Longtin (julial@turinglace.com)
-- Released under the GNU AGPLV3+, see LICENSE
module ParserSpec.Expr (exprSpec) where
import Test.Hspec
import Graphics.Implicit.ExtOpenScad.Definitions
import Graphics.Implicit.ExtOpenScad.Parser.Expr
import Graphics.Implicit.ExtOpenScad.Parser.Statement
import ParserSpec.Util
import Text.ParserCombinators.Parsec hiding (State)
import Data.Either
-- Be explicit about what we import.
import Prelude (Bool(True, False), ($))
infixr 1 -->
(-->) :: String -> Expr -> Expectation
(-->) source expr =
(parseExpr source) `shouldBe` Right expr
-- Hspec, for writing specs.
import Test.Hspec (describe, Expectation, Spec, it, pendingWith, specify)
infixr 1 -->+
(-->+) :: String -> (Expr, String) -> Expectation
(-->+) source (result, leftover) =
(parseWithLeftOver expr0 source) `shouldBe` (Right (result, leftover))
-- Parsed expression components.
import Graphics.Implicit.ExtOpenScad.Definitions (Expr(Var, ListE, (:$)), Pattern(Name))
-- The type used for variables, in ImplicitCAD.
import Graphics.Implicit.Definitions ()
-- Our utility library, for making these tests easier to read.
import ParserSpec.Util ((-->), fapp, num, bool, stringLiteral, plus, minus, mult, modulo, power, divide, negate, and, or, not, gt, lt, ternary, append, index, lambda, parseWithLeftOver)
-- Default all numbers in this file to being of the type ImplicitCAD uses for values.
default ()
ternaryIssue :: Expectation -> Expectation
ternaryIssue _ = pendingWith "parser doesn't handle ternary operator correctly"
negationIssue :: Expectation -> Expectation
negationIssue _ = pendingWith "parser doesn't handle negation operator correctly"
logicalSpec :: Spec
logicalSpec = do
it "handles not" $ "!foo" --> (app' "!" [Var "foo"])
describe "not" $ do
specify "single" $ "!foo" --> not [Var "foo"]
specify "multiple" $
negationIssue $ "!!!foo" --> not [not [not [Var "foo"]]]
it "handles and/or" $ do
"foo && bar" --> app' "&&" [Var "foo", Var "bar"]
"foo || bar" --> app' "||" [Var "foo", Var "bar"]
"foo && bar" --> and [Var "foo", Var "bar"]
"foo || bar" --> or [Var "foo", Var "bar"]
describe "ternary operator" $ do
specify "with primitive expressions" $
"x ? 2 : 3" --> app' "?" [Var "x", num 2, num 3]
"x ? 2 : 3" --> ternary [Var "x", num 2, num 3]
specify "with parenthesized comparison" $
"(1 > 0) ? 5 : -5" --> app' "?" [app' ">" [num 1, num 0], num 5, num (-5)]
"(1 > 0) ? 5 : -5" --> ternary [gt [num 1, num 0], num 5, num (-5)]
specify "with comparison in head position" $
ternaryIssue $ "1 > 0 ? 5 : -5" --> app' "?" [app' ">" [num 1, num 0], num 5, num (-5)]
ternaryIssue $ "1 > 0 ? 5 : -5" --> ternary [gt [num 1, num 0], num 5, num (-5)]
specify "with comparison in head position, and addition in tail" $
ternaryIssue $ "1 > 0 ? 5 : 1 + 2" -->
app' "?" [app' ">" [num 1, num 0], num 5, app "+" [num 1, num 2]]
ternary [gt [num 1, num 0], num 5, plus [num 1, num 2]]
literalSpec :: Spec
literalSpec = do
it "handles integers" $ do
it "handles integers" $
"12356" --> num 12356
it "handles floats" $ do
it "handles positive leading zero integers" $ do
"000012356" --> num 12356
it "handles zero integer" $ do
"0" --> num 0
it "handles leading zero integer" $ do
"0000" --> num 0
it "handles floats" $
"23.42" --> num 23.42
describe "booleans" $ do
it "accepts true" $ "true" --> bool True
it "accepts false" $ "false" --> bool False
letBindingSpec :: Spec
letBindingSpec = do
it "handles let with integer binding and spaces" $ do
"let ( a = 1 ) a" --> lambda [Name "a"] (Var "a") [num 1]
it "handles multiple variable let" $ do
"let (a = x, b = y) a + b" --> lambda [Name "a"] ((lambda [Name "b"] (plus [Var "a", Var "b"])) [Var "y"]) [Var "x"]
it "handles empty let" $ do
"let () a" --> (Var "a")
it "handles nested let" $ do
"let(a=x) let(b = y) a + b" --> lambda [Name "a"] ((lambda [Name "b"] (plus [Var "a", Var "b"])) [Var "y"]) [Var "x"]
exprSpec :: Spec
exprSpec = do
describe "literals" literalSpec
describe "identifiers" $ do
describe "identifiers" $
it "accepts valid variable names" $ do
"foo" --> Var "foo"
"foo_bar" --> Var "foo_bar"
describe "literals" $ literalSpec
describe "grouping" $ do
it "allows parens" $ do
it "allows parens" $
"( false )" --> bool False
it "handles vectors" $ do
it "handles vectors" $
"[ 1, 2, 3 ]" --> ListE [num 1, num 2, num 3]
it "handles lists" $ do
it "handles empty vectors" $ do
"[]" --> ListE []
it "handles single element vectors" $ do
"[a]" --> ListE [Var "a"]
it "handles nested vectors" $ do
"[ 1, [2, 7], [3, 4, 5, 6] ]" --> ListE [num 1, ListE [num 2, num 7], ListE [num 3, num 4, num 5, num 6]]
it "handles lists" $
"( 1, 2, 3 )" --> ListE [num 1, num 2, num 3]
it "handles generators" $
"[ a : 1 : b + 10 ]" -->
(app "list_gen" [Var "a", num 1, app "+" [Var "b", num 10]])
"[ a : b ]" -->
fapp "list_gen" [Var "a", Var "b"]
it "handles generators with expression" $
"[ a : b + 10 ]" -->
fapp "list_gen" [Var "a", plus [Var "b", num 10]]
it "handles increment generators" $
"[ a : 3 : b + 10 ]" -->
fapp "list_gen" [Var "a", num 3, plus [Var "b", num 10]]
it "handles indexing" $
"foo[23]" --> Var "index" :$ [Var "foo", num 23]
"foo[23]" --> index [Var "foo", num 23]
it "handles multiple indexes" $
"foo[23][12]" --> Var "index" :$ [Var "index" :$ [Var "foo", num 23], num 12]
it "handles single function call with single argument" $
"foo(1)" --> Var "foo" :$ [num 1]
it "handles single function call with multiple arguments" $
"foo(1, 2, 3)" --> Var "foo" :$ [num 1, num 2, num 3]
it "handles multiple function calls" $
"foo(1)(2)(3)" --> ((Var "foo" :$ [num 1]) :$ [num 2]) :$ [num 3]
describe "arithmetic" $ do
it "handles unary +/-" $ do
"-42" --> num (-42)
"+42" --> num 42
it "handles unary - with extra spaces" $ do
"- 42" --> num (-42)
it "handles unary + with extra spaces" $ do
"+ 42" --> num 42
it "handles unary - with parentheses" $ do
"-(4 - 3)" --> negate [ minus [num 4, num 3]]
it "handles unary + with parentheses" $ do
"+(4 - 1)" --> minus [num 4, num 1]
it "handles unary - with identifier" $ do
"-foo" --> negate [Var "foo"]
it "handles unary + with identifier" $ do
"+foo" --> Var "foo"
it "handles unary - with string literal" $ do
"-\"foo\"" --> negate [stringLiteral "foo"]
it "handles unary + with string literal" $ do
"+\"foo\"" --> stringLiteral "foo"
it "handles +" $ do
"1 + 2" --> app "+" [num 1, num 2]
"1 + 2 + 3" --> app "+" [num 1, num 2, num 3]
"1 + 2" --> plus [num 1, num 2]
"1 + 2 + 3" --> plus [num 1, num 2, num 3]
it "handles -" $ do
"1 - 2" --> app' "-" [num 1, num 2]
"1 - 2 - 3" --> app' "-" [app' "-" [num 1, num 2], num 3]
"1 - 2" --> minus [num 1, num 2]
"1 - 2 - 3" --> minus [minus [num 1, num 2], num 3]
it "handles +/- in combination" $ do
"1 + 2 - 3" --> app "+" [num 1, app' "-" [num 2, num 3]]
"2 - 3 + 4" --> app "+" [app' "-" [num 2, num 3], num 4]
"1 + 2 - 3 + 4" --> app "+" [num 1, app' "-" [num 2, num 3], num 4]
"1 + 2 - 3 + 4 - 5 - 6" --> app "+" [num 1,
app' "-" [num 2, num 3],
app' "-" [app' "-" [num 4, num 5],
"1 + 2 - 3" --> plus [num 1, minus [num 2, num 3]]
"2 - 3 + 4" --> plus [minus [num 2, num 3], num 4]
"1 + 2 - 3 + 4" --> plus [num 1, minus [num 2, num 3], num 4]
"1 + 2 - 3 + 4 - 5 - 6" --> plus [num 1,
minus [num 2, num 3],
minus [minus [num 4, num 5],
num 6]]
it "handles exponentiation" $
"x ^ y" --> app' "^" [Var "x", Var "y"]
"x ^ y" --> power [Var "x", Var "y"]
it "handles multiple exponentiations" $
"x ^ y ^ z" --> power [Var "x", power [Var "y", Var "z"]]
it "handles *" $ do
"3 * 4" --> app "*" [num 3, num 4]
"3 * 4 * 5" --> app "*" [num 3, num 4, num 5]
"3 * 4" --> mult [num 3, num 4]
"3 * 4 * 5" --> mult [num 3, num 4, num 5]
it "handles /" $
"4.2 / 2.3" --> app' "/" [num 4.2, num 2.3]
"4.2 / 2.3" --> divide [num 4.2, num 2.3]
it "handles precedence" $
parseExpr "1 + 2 / 3 * 5" `shouldBe`
(Right $ app "+" [num 1, app "*" [app' "/" [num 2, num 3], num 5]])
it "handles append" $
parseExpr "foo ++ bar ++ baz" `shouldBe`
(Right $ app "++" [Var "foo", Var "bar", Var "baz"])
"1 + 2 / 3 * 5" --> plus [num 1, mult [divide [num 2, num 3], num 5]]
it "handles append" $
"foo ++ bar ++ baz" --> append [Var "foo", Var "bar", Var "baz"]
describe "logical operators" logicalSpec
describe "let expressions" letBindingSpec
describe "application" $ do
specify "base case" $ "foo(x)" --> Var "foo" :$ [Var "x"]
specify "multiple arguments" $
"foo(x, 1, 2)" --> Var "foo" :$ [Var "x", num 1, num 2]
specify "multiple" $
"foo(x, 1, 2)(5)(y)" --> ((Var "foo" :$ [Var "x", num 1, num 2]) :$ [num 5]) :$ [Var "y"]
specify "multiple, with indexing" $
"foo(x)[0](y)" --> (index [Var "foo" :$ [Var "x"], num 0] :$ [Var "y"])

View File

@ -1,82 +1,97 @@
-- Implicit CAD. Copyright (C) 2011, Christopher Olah (chris@colah.ca)
-- Copyright (C) 2014-2017, Julia Longtin (julial@turinglace.com)
-- Released under the GNU AGPLV3+, see LICENSE
-- | Statement related hspec tests.
module ParserSpec.Statement (statementSpec) where
import Test.Hspec
import Text.ParserCombinators.Parsec hiding (State)
import ParserSpec.Util
import Graphics.Implicit.ExtOpenScad.Definitions
import Graphics.Implicit.ExtOpenScad.Parser.Statement
import Data.Either
import Prelude (String, Maybe(Just), Bool(True), ($))
parsesAs :: String -> [StatementI] -> Expectation
parsesAs source stmts =
(parseProgram "src" source) `shouldBe` Right stmts
import Test.Hspec (Spec, Expectation, shouldBe, shouldSatisfy, it, pendingWith, describe)
import ParserSpec.Util (bool, num, minus, plus, mult, index)
import Graphics.Implicit.ExtOpenScad.Definitions (StatementI(StatementI), Symbol, Expr(ListE, LamE, Var, (:$)), Statement(NewModule, ModuleCall, If, (:=)), Pattern(Name, ListP))
-- Parse an ExtOpenScad program.
import Graphics.Implicit.ExtOpenScad.Parser.Statement (parseProgram)
import Data.Either (Either(Right), isLeft)
import Text.ParserCombinators.Parsec (Line, Column)
-- | an expectation that a string is equivalent to a statement.
infixr 1 -->
(-->) :: String -> [StatementI] -> Expectation
(-->) source stmts =
parseProgram source `shouldBe` Right stmts
-- | an expectation that a string generates an error.
parsesAsError :: String -> Expectation
parsesAsError source =
(parseProgram "src" source) `shouldSatisfy` isLeft
parsesAsError source = parseProgram source `shouldSatisfy` isLeft
-- | A single statement.
single :: Statement StatementI -> [StatementI]
single st = [StatementI 1 st]
single st = [StatementI 1 1 st]
call :: Symbol -> [(Maybe Symbol, Expr)] -> [StatementI] -> StatementI
call name args stmts = StatementI 1 (ModuleCall name args stmts)
-- | A function call.
call :: Symbol -> Column -> [(Maybe Symbol, Expr)] -> [StatementI] -> StatementI
call name position args stmts = StatementI 1 position (ModuleCall name args stmts)
-- | Test a simple if block.
ifSpec :: Spec
ifSpec = do
it "parses" $
"if (true) { a(); } else { b(); }" `parsesAs` (
single $ If (bool True) [call "a" [] []] [call "b" [] []])
ifSpec = it "parses" $
"if (true) { a(); } else { b(); }" -->
single ( If (bool True) [call "a" 13 [] []] [call "b" 27 [] []])
-- | Test assignments.
assignmentSpec :: Spec
assignmentSpec = do
it "parses correctly" $
"y = -5;" `parsesAs` (single $ Name "y" := (num (-5)))
"y = -5;" --> single ( Name "y" := num (-5))
it "handles pattern matching" $
"[x, y] = [1, 2];" `parsesAs`
(single $ ListP [Name "x", Name "y"] := (ListE [num 1, num 2]))
it "handles function definitions" $
"foo (x, y) = x * y;" `parsesAs` single fooFunction
it "handles the function keyword" $
"function foo(x, y) = x * y;" `parsesAs` single fooFunction
it "nested indexing" $
"x = [y[0] - z * 2];" `parsesAs`
(single $ Name "x" := ListE [app' "-" [app' "index" [Var "y", num 0],
app "*" [Var "z", num 2]]])
"[x, y] = [1, 2];" --> single (ListP [Name "x", Name "y"] := ListE [num 1, num 2])
it "handles the function keyword and definitions" $
"function foo(x, y) = x * y;" --> single fooFunction
it "handles function with let expression" $
"function withlet(b) = let (c = 5) b + c;" -->
(single $ (Name "withlet" := LamE [Name "b"] (LamE [Name "c"] (plus [Var "b", Var "c"]) :$ [num 5])))
it "handles nested indexing" $
"x = [y[0] - z * 2];" -->
single ( Name "x" := ListE [minus [index [Var "y", num 0],
mult [Var "z", num 2]]])
where
fooFunction = Name "foo" := (LamE [Name "x", Name "y"]
(app "*" [Var "x", Var "y"]))
fooFunction :: Statement st
fooFunction = Name "foo" := LamE [Name "x", Name "y"]
(mult [Var "x", Var "y"])
-- | the parser fails on as empty file. This can't be right.
emptyFileIssue :: Expectation -> Expectation
emptyFileIssue _ = pendingWith "parser should probably allow empty files"
-- | Our entry points. Test all of the statements.
statementSpec :: Spec
statementSpec = do
describe "empty file" $ do
describe "empty file" $
it "returns an empty list" $
emptyFileIssue $ "" `parsesAs` []
describe "line comment" $ do
it "parses as empty" $ emptyFileIssue $ "// foish bar\n" `parsesAs` []
describe "module call" $ do
it "parses" $
"foo();" `parsesAs` (single $ ModuleCall "foo" [] [])
describe "difference of two cylinders" $ do
emptyFileIssue $ "" --> []
describe "assignment" assignmentSpec
describe "if" ifSpec
describe "line comment" $
it "parses as empty" $ emptyFileIssue $ "// foish bar\n" --> []
describe "module call" $
it "parses" $ "foo();" --> single (ModuleCall "foo" [] [])
describe "difference of two cylinders" $
it "parses correctly" $
"difference(){ cylinder(r=5,h=20); cylinder(r=2,h=20); }"
`parsesAs` single (
--> single (
ModuleCall "difference" [] [
(call "cylinder" [(Just "r", num 5.0),
call "cylinder" 15 [(Just "r", num 5.0),
(Just "h", num 20.0)]
[]),
(call "cylinder" [(Just "r", num 2.0),
[],
call "cylinder" 35 [(Just "r", num 2.0),
(Just "h", num 20.0)]
[])])
describe "empty module definition" $ do
[]])
describe "empty module definition" $
it "parses correctly" $
"module foo_bar() {}" `parsesAs` (single $ NewModule "foo_bar" [] [])
describe "assignment" assignmentSpec
describe "if" ifSpec
"module foo_bar() {}" --> single (NewModule "foo_bar" [] [])

View File

@ -1,45 +1,114 @@
-- Implicit CAD. Copyright (C) 2011, Christopher Olah (chris@colah.ca)
-- Copyright 2014 2015 2016, Julia Longtin (julial@turinglace.com)
-- Copyright 2015 2016, Mike MacHenry (mike.machenry@gmail.com)
-- Released under the GNU AGPLV3+, see LICENSE
-- Allow us to use explicit foralls when writing function type declarations.
{-# LANGUAGE ExplicitForAll #-}
-- Utilities
module ParserSpec.Util
( num
( (-->)
, (-->+)
, num
, bool
, app
, app'
, parseWithEof
, stringLiteral
, fapp
, plus
, minus
, mult
, modulo
, power
, divide
, not
, and
, or
, gt
, lt
, negate
, ternary
, append
, index
, lambda
, parseWithLeftOver
, parseExpr
) where
import Graphics.Implicit.Definitions
import Graphics.Implicit.ExtOpenScad
import Graphics.Implicit.ExtOpenScad.Definitions
import Graphics.Implicit.ExtOpenScad.Parser.Expr
import Text.Parsec.String
import Text.Parsec.Error
import Text.ParserCombinators.Parsec hiding (State)
import Control.Applicative ((<$>), (<*>), (<*), (*>))
-- be explicit about where we get things from.
import Prelude (Bool, String, Either, (<), ($), (.), (<*), otherwise)
-- The datatype of positions in our world.
import Graphics.Implicit.Definitions ()
-- The datatype of expressions, symbols, and values in the OpenScad language.
import Graphics.Implicit.ExtOpenScad.Definitions (Expr(LitE, (:$), Var, ListE, LamE), OVal(ONum, OBool, OString), Pattern)
import Text.ParserCombinators.Parsec (Parser, ParseError, parse, manyTill, anyChar, eof)
import Control.Applicative ((<$>), (<*>))
import Test.Hspec (Expectation, shouldBe)
import Data.Either (Either(Right))
-- the expression parser entry point.
import Graphics.Implicit.ExtOpenScad.Parser.Expr (expr0)
-- An operator for expressions for "the left side should parse to the right side."
infixr 1 -->
(-->) :: String -> Expr -> Expectation
(-->) source expr =
parse (expr0 <* eof) "<expr>" source `shouldBe` Right expr
-- An operator for expressions for "the left side should parse to the right side, and some should be left over".
infixr 1 -->+
(-->+) :: String -> (Expr, String) -> Expectation
(-->+) source (result, leftover) =
parseWithLeftOver expr0 source `shouldBe` Right (result, leftover)
-- | Types
num :: -> Expr
num x
-- note that the parser should handle negative number literals
-- FIXME: the parser should handle negative number literals
-- directly, we abstract that deficiency away here
| x < 0 = app' "negate" [LitE $ ONum (-x)]
| x < 0 = oapp "negate" [LitE $ ONum (-x)]
| otherwise = LitE $ ONum x
bool :: Bool -> Expr
bool = LitE . OBool
-- Operators and functions need two different kinds of applications
app :: String -> [Expr] -> Expr
app name args = Var name :$ [ListE args]
stringLiteral :: String -> Expr
stringLiteral = LitE . OString
app' :: Symbol -> [Expr] -> Expr
app' name args = Var name :$ args
-- | Operators
plus,minus,mult,modulo,power,divide,negate,and,or,not,gt,lt,ternary,append,index :: [Expr] -> Expr
minus = oapp "-"
modulo = oapp "%"
power = oapp "^"
divide = oapp "/"
and = oapp "&&"
or = oapp "||"
not = oapp "!"
gt = oapp ">"
lt = oapp "<"
ternary = oapp "?"
negate = oapp "negate"
index = oapp "index"
plus = fapp "+"
mult = fapp "*"
append = fapp "++"
-- | we need two different kinds of application functions
oapp,fapp :: String -> [Expr] -> Expr
oapp name args = Var name :$ args
fapp name args = Var name :$ [ListE args]
lambda :: [Pattern] -> Expr -> [Expr] -> Expr
lambda params expr args = LamE params expr :$ args
parseWithLeftOver :: Parser a -> String -> Either ParseError (a, String)
parseWithLeftOver p = parse ((,) <$> p <*> leftOver) ""
where leftOver = manyTill anyToken eof
parseWithEof :: Parser a -> String -> String -> Either ParseError a
parseWithEof p = parse (p <* eof)
parseExpr :: String -> Either ParseError Expr
parseExpr = parseWithEof expr0 "expr"
where
leftOver :: Parser String
leftOver = manyTill anyChar eof