tsf 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8dd49df524555e9c11369bba05b4c55855a972ead2493da6bd03aedb5ffbee55
4
+ data.tar.gz: 94d338f36c27841fa5efc52b83060c3788a01fd5df5687be82db1b0e260f88e3
5
+ SHA512:
6
+ metadata.gz: 11c7ba675c35eb3be2f35d08e3f9f694a5336d87994f8ab338cdeafa0f67ac9507be1674be52dd0bfb456f8d15a62a3e85bbaaeb1d4c40f3d331e1448e060f87
7
+ data.tar.gz: ab80180d202b2f7ab15b956c86488b3aca4a90fe7aa5cfa5b75fe9438fd4d0d26a45f631e220b1baf73f63003f2c9f00fa70f6d4a9e9116f29a4919288e880ed
data/README.md ADDED
@@ -0,0 +1,23 @@
1
+ # TSF.rb
2
+ Ruby parser for Trotect's TSF file format. The TSF file format is the main format of [Trotec's JobControl software](https://www.troteclaser.com/de/lasermaschinen/jobcontrol). JobControl is the key piece of software between a design in illustrator and a trotec laser machine. To create a TSF, you _print_ from design software to JobControl to generate a TSF with vector lines and bitmaps for cutting and engraving. Typically these files are only previewable by JobControl, making it difficult to share & reuse files within an organisation with confidence.
3
+
4
+ This Ruby Gem is used as an interface with the TSF file format to read metadata, bitmap, and polygon data, as well as generate previews of the files without the need for JobControl or design software.
5
+
6
+ # Get started
7
+ ## Instalation
8
+
9
+ ```bash
10
+ bundle install tsf
11
+ ```
12
+
13
+ ## How to use
14
+
15
+ ```ruby
16
+ require 'tsf'
17
+
18
+ data = File.read('to/your/file.tsf')
19
+ vector = TSF::Vector.load_tsf(data)
20
+
21
+ vector.loaded
22
+ # => True
23
+ ```
@@ -0,0 +1,14 @@
1
+ module Converter
2
+ # Converts a TSF file to an image format (PNG, JPG, etc.)
3
+ class TsfToImage
4
+ def initialize(tsf_file_path)
5
+ @tsf_file_path = tsf_file_path
6
+ end
7
+
8
+ def convert_to_image(output_format)
9
+ # Logic to convert TSF to image
10
+ puts "Converting #{@tsf_file_path} to #{output_format}..."
11
+ # Conversion logic goes here
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module Converter
2
+ # Converts a TSF file to an SVG format
3
+ class TsfToSvg
4
+ def initialize(tsf_file_path)
5
+ @tsf_file_path = tsf_file_path
6
+ end
7
+
8
+ def convert_to_svg
9
+ # Logic to convert TSF to SVG
10
+ puts "Converting #{@tsf_file_path} to SVG..."
11
+ # Conversion logic goes here
12
+ end
13
+ end
14
+ end
data/lib/tsf/vector.rb ADDED
@@ -0,0 +1,168 @@
1
+ module Tsf
2
+ # Loads a TSF file and parses its content
3
+ class Vector
4
+ attr_reader :loaded, :grouped_data, :polygons, :headers
5
+ attr_reader :material_group, :material_name, :job_name, :job_number, :resolution, :size, :bitmap
6
+
7
+ # Initialize the Vector class
8
+ def initialize
9
+ @loaded = false
10
+ @grouped_data = nil
11
+ @polygons = []
12
+ @headers = []
13
+
14
+ ## Header information. Deconstructed.
15
+ # Material settings for the job.
16
+ @material_group = nil
17
+ @material_name = nil
18
+
19
+ # Job name as set in the Trotec software.
20
+ @job_name = nil
21
+ @job_number = nil
22
+
23
+ # Usualy 500 DPI.
24
+ @resoultion = nil
25
+ # Will be set artboard size. when creating the tsf.
26
+ @size = nil
27
+
28
+ ## Image Data
29
+ # Images in TSF files are stored as a monochrome bitmap.
30
+ # The bitmap is used to determine the engraving area.
31
+ # Image data, if present.
32
+ @bitmap = nil
33
+
34
+ #####
35
+
36
+ # Minimum required variables to be present in the TSF file.
37
+ # CamelCase
38
+ @min_vars = ['MaterialGroup', 'MaterialName', 'JobName', 'JobNumber', 'Resolution', 'Size']
39
+ end
40
+
41
+ # Data is the raw data from the TSF file.
42
+ # data = File.read('path/to/file.tsf')
43
+ def self.load_tsf(data)
44
+ vector = new
45
+ if data.is_a?(String)
46
+
47
+ # Check if the data is empty
48
+ if data.empty?
49
+ raise ArgumentError, "Data is empty. Please provide a valid TSF file."
50
+ end
51
+
52
+ # Parse vector data
53
+ vector.parse_tsf(data)
54
+ else
55
+ raise ArgumentError, "Expected a String, got #{data.class}"
56
+ end
57
+ vector
58
+ end
59
+
60
+
61
+ def parse_tsf(data)
62
+ # Tsf files are text formatted files that repsresnte vector paths for a Troctec laser to follow. Similar to that of Gcode.
63
+ # TSF file use a xml-like format the prefix "BegGroup" and "EndGroup" to denote the start and end of a group of commands or meta data.
64
+ # TSF files are structed it to 4 top level groups.
65
+ # - Header - Contains the header information of the file.
66
+ # - JobMeta - Contains meta data about the job, such as the name, date, and other information.
67
+ # - Bitmap - Contains the engraving data, such as the image to be engraved.
68
+ # - DrawCommands - Contains the vector data, such as the paths to be followed by the laser.
69
+ # - contains many "DrawPolygon" commands, which are the actual vector paths to be followed by the laser.
70
+ # - Each "DrawPolygon" command contains a list of points, which are the coordinates of the points to be followed by the laser. Delimtited by a ;.
71
+ # - The first 4 digits of the command contain, ID, then R,G,B color values of the Polygon.
72
+
73
+ @grouped_data = get_grouped_data(data)
74
+
75
+ # Check if the grouped data has the required groups
76
+ # A TSF file is not complete if it does not have a header and draw commands.
77
+ unless @grouped_data.key?('Header') || @grouped_data.key?('DrawCommands')
78
+ raise ArgumentError, "Invalid TSF data. Missing required groups core groups: Header, DrawCommands"
79
+ end
80
+
81
+ # Check if the grouped data has the required keys
82
+ missing_headers = @min_vars - @grouped_data['Header'].keys
83
+ if missing_headers.any?
84
+ raise ArgumentError, "Invalid TSF data. Missing required headers: #{missing_headers.join(', ')}"
85
+ end
86
+
87
+
88
+ @material_group = @grouped_data['Header']['MaterialGroup']
89
+ @material_name = @grouped_data['Header']['MaterialName']
90
+
91
+ @job_name = @grouped_data['Header']['JobName']
92
+ @job_number = @grouped_data['Header']['JobNumber']
93
+
94
+ @resolution = @grouped_data['Header']['Resolution'].to_i
95
+ @size = @grouped_data['Header']['Size'].split(";").map(&:to_f)
96
+
97
+ @polygons = get_polygons(@grouped_data)
98
+
99
+ @loaded = true
100
+ @grouped_data
101
+ end
102
+
103
+ def get_grouped_data(data)
104
+ # Groups are delimited by the "BegGroup" and "EndGroup" tags.
105
+ # Each group contains a list of commands or meta data.
106
+ # The normal groups are:
107
+ # - Header
108
+ # - JobMeta
109
+ # - Bitmap (optional)
110
+ # - DrawCommands
111
+
112
+ # Auto create nested hash when accessed.
113
+ nested_group_and_values = Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) }
114
+ # Typically, we only get 2 levels of groups. But we can have more.
115
+ current_groups = []
116
+
117
+ data.split("\n").each do |line|
118
+ # a line is a command and a value. ie. <JobNumber: 1234> or <DrawPolygon: 12;0;1;2;3>
119
+ # a line can define a group where value is the group name. ie. <BegGroup: Header> <EndGroup: Header>
120
+ # groups can have groups (max 2.)
121
+ # groups can have multple commands.
122
+ # a command has only one value.
123
+
124
+ command, value = line.split(":").map(&:strip).map { |s| s.gsub(/<|>/, '') }
125
+
126
+ case command
127
+ when "BegGroup"
128
+ # Start a new group
129
+ current_groups << value
130
+ when "EndGroup"
131
+ # End the current group
132
+ current_groups.pop
133
+ else
134
+ # Assign the command and value to the appropriate group
135
+ if current_groups.any?
136
+ current = nested_group_and_values.dig(*current_groups)
137
+ current[command] = value
138
+ end
139
+ end
140
+
141
+ end
142
+ nested_group_and_values
143
+ end
144
+
145
+ def get_polygons(grouped_data)
146
+ polygons = []
147
+ grouped_data['DrawCommands'].each do |key, value|
148
+ polygon_string = value['DrawPolygon']
149
+ polygon_array = polygon_string.split(";")
150
+
151
+ polygon = {
152
+ 'id': polygon_array[0].to_i,
153
+ 'color': {
154
+ 'r': polygon_array[1].to_i,
155
+ 'g': polygon_array[2].to_i,
156
+ 'b': polygon_array[3].to_i,
157
+ },
158
+ 'points': []
159
+ }
160
+
161
+ polygon[:data] = polygon_array.drop(4).map(&:to_i).each_slice(2).to_a
162
+
163
+ polygons << polygon
164
+ end
165
+ polygons
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,3 @@
1
+ module Tsf
2
+ version = "0.1.0"
3
+ end
data/lib/tsf.rb ADDED
@@ -0,0 +1,10 @@
1
+ # Primary Loader
2
+ require_relative 'tsf/vector'
3
+
4
+ # Conversion tools
5
+ require_relative 'tsf/convert/tsf_to_image'
6
+ require_relative 'tsf/convert/tsf_to_svg'
7
+
8
+
9
+ module Tsf
10
+ end
metadata ADDED
@@ -0,0 +1,45 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tsf
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Isaac Bonora
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2025-04-14 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: Ruby parser for Trotec's TSF file format.
13
+ email: isaac@isbonora.com
14
+ executables: []
15
+ extensions: []
16
+ extra_rdoc_files: []
17
+ files:
18
+ - README.md
19
+ - lib/tsf.rb
20
+ - lib/tsf/convert/tsf_to_image.rb
21
+ - lib/tsf/convert/tsf_to_svg.rb
22
+ - lib/tsf/vector.rb
23
+ - lib/tsf/version.rb
24
+ homepage: https://rubygems.org/gems/tsfrb
25
+ licenses:
26
+ - MIT
27
+ metadata: {}
28
+ rdoc_options: []
29
+ require_paths:
30
+ - lib
31
+ required_ruby_version: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: '0'
36
+ required_rubygems_version: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ requirements: []
42
+ rubygems_version: 3.6.6
43
+ specification_version: 4
44
+ summary: Ruby parser for Trotec's TSF file format.
45
+ test_files: []