ruby3mf 0.2.4 → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e46e2c266255e1ff3e37964c3cce6306f9361ced
4
- data.tar.gz: fcb133940bece74939393383af9c4f0df4234ba2
3
+ metadata.gz: e6ed7b5e499cd264c483fb07b8b64b2e87d66468
4
+ data.tar.gz: 7e4d586f7171056fc27a30295fb778c278f0cda1
5
5
  SHA512:
6
- metadata.gz: d1569f55c29cc7b3617db2eea3bc529081dc6408a4191f38c781dc25620156ed4c5492a87a6ff3ca166af7dd2740ba7089cb29063286a3bb7c81e3f8d6477a0c
7
- data.tar.gz: c31e67696eebe5f88a65f3cce3ebc591d6792432039259dac4bba7f856479c5f1ee5b6b9ffe689e758397006a8127d6c2f5f7ed35324debd68abf000e5211453
6
+ metadata.gz: 65fea6f8723fd942bb26876ccc2fdb74877a0c441264979e1ee1d88f2416085a701b023553a40d5a2cc5ce51a5ff1ae62aae6f6c5968920ddf94a99938892640
7
+ data.tar.gz: 5666f43cf880c92b2a2a583375c6eb4a64598e75b1ea3d4e182beddcaec4f07841027241bcf08ca194bc9a27d208b6b03eb2c66010b58da199df2d7ae338737d
data/.gitignore CHANGED
@@ -11,3 +11,4 @@
11
11
  /spec/ruby3mf-testfiles/
12
12
  /profile-run/
13
13
  .DS_Store
14
+ suite_test.txt
data/bin/suite_test.sh CHANGED
@@ -6,7 +6,9 @@ GOOD_FILES=../3mf-test-suite/Positive/${MATCH}*.3mf
6
6
  BAD_FILES=../3mf-test-suite/Negative/${MATCH}*.3mf
7
7
 
8
8
  echo "Test Suite: filter: ${MATCH}"
9
- printf "\n\nTest Suite: filter: ${MATCH} - $(date)\n" >> ${OUTFILE}
9
+ printf "\n\n==============================================================================="
10
+ printf "\n\n Test Suite: filter: ${MATCH} - $(date)\n" >> ${OUTFILE}
11
+ printf "\n\n==============================================================================="
10
12
 
11
13
  echo "Positive Files -------------"
12
14
  printf "\nPositive Files -------------\n" >> ${OUTFILE}
@@ -35,5 +37,5 @@ for filename in ${BAD_FILES}; do
35
37
  done
36
38
 
37
39
  echo "All Done."
38
- printf "Completed -- $(date)\n\n" >> ${OUTFILE}
40
+ printf "\nCompleted -- $(date)\n\n" >> ${OUTFILE}
39
41
 
@@ -71,7 +71,7 @@ Items within this schema follow a simple naming convention of appending a prefix
71
71
  <xs:sequence>
72
72
  <xs:element ref="vertices"/>
73
73
  <xs:element ref="triangles"/>
74
- <xs:any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="30000"/>
74
+ <xs:any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
75
75
  </xs:sequence>
76
76
  </xs:complexType>
77
77
  <xs:complexType name="CT_Vertices">
@@ -87,7 +87,7 @@ Items within this schema follow a simple naming convention of appending a prefix
87
87
  </xs:complexType>
88
88
  <xs:complexType name="CT_Triangles">
89
89
  <xs:sequence>
90
- <xs:element ref="triangle" minOccurs="1" maxOccurs="30000"/>
90
+ <xs:element ref="triangle" minOccurs="1" maxOccurs="unbounded"/>
91
91
  </xs:sequence>
92
92
  </xs:complexType>
93
93
  <xs:complexType name="CT_Triangle">
@@ -62,10 +62,10 @@ class ContentTypes
62
62
  end
63
63
  end
64
64
  required_content_types.each do |req_type|
65
- l.error "[Content_Types].xml is missing required ContentType \"#{req_type}\"", page: 10 unless found_types.values.include? req_type
65
+ l.error :invalid_content_type, mt: req_type unless found_types.values.include? req_type
66
66
  end
67
67
  rescue Nokogiri::XML::SyntaxError => e
68
- l.error "[Content_Types].xml file is not valid XML. #{e}", page: 15
68
+ l.error :content_types_invalid_xml, e: "#{e}"
69
69
  end
70
70
  end
71
71
  return new(found_types, found_overrides)
@@ -60,6 +60,7 @@ class Document
60
60
  Log3mf.context 'zip' do |l|
61
61
  begin
62
62
  Zip.warn_invalid_date = false
63
+ Zip.unicode_names = true
63
64
 
64
65
  # check for the general purpose flag set - if so, warn that 3mf may not work on some systems
65
66
  File.open(input_file, "r") do |file|
@@ -77,13 +78,7 @@ class Document
77
78
  zip_file.each do |part|
78
79
  l.context "part names /#{part.name}" do |l|
79
80
  unless part.name.end_with? '[Content_Types].xml'
80
- begin
81
- u = URI part.name
82
- rescue ArgumentError, URI::InvalidURIError
83
- l.fatal_error "This NEVER Happens! mdw 12Jan2017"
84
- l.error :err_uri_bad
85
- next
86
- end
81
+ next unless u = parse_uri(l, part.name)
87
82
 
88
83
  u.path.split('/').each do |segment|
89
84
  l.error :err_uri_hidden_file if (segment.start_with? '.') && !(segment.end_with? '.rels')
@@ -98,7 +93,7 @@ class Document
98
93
  if content_type_match
99
94
  m.types = ContentTypes.parse(content_type_match)
100
95
  else
101
- l.fatal_error 'Missing required file: [Content_Types].xml', page: 4
96
+ l.fatal_error :missing_content_types
102
97
  end
103
98
  end
104
99
 
@@ -109,6 +104,17 @@ class Document
109
104
  zip_file.glob('**/*.rels').each do |rel|
110
105
  m.relationships[rel.name] = Relationships.parse(rel)
111
106
  end
107
+
108
+ root_rels = m.relationships['_rels/.rels']
109
+ unless root_rels.nil?
110
+ start_part_rel = root_rels.select { |rel| rel[:type] == Document::MODEL_TYPE }.first
111
+ if start_part_rel
112
+ start_part_target = start_part_rel[:target]
113
+ start_part_types = m.relationships.flat_map { |k, v| v }.select { |rel| rel[:type] == Document::MODEL_TYPE && rel[:target] == start_part_target }
114
+ l.error :invalid_startpart_target, :target => start_part_target if start_part_types.size > 1
115
+ end
116
+ end
117
+
112
118
  end
113
119
 
114
120
  l.context "print tickets" do |l|
@@ -120,22 +126,23 @@ class Document
120
126
  m.relationships.each do |file_name, rels|
121
127
  rels.each do |rel|
122
128
  l.context rel[:target] do |l|
123
-
124
- begin
125
- u = URI rel[:target]
126
- rescue URI::InvalidURIError
127
- l.error :err_uri_bad
128
- next
129
- end
129
+ next unless u = parse_uri(l, rel[:target])
130
130
 
131
131
  l.error :err_uri_relative_path unless u.to_s.start_with? '/'
132
132
 
133
133
  target = rel[:target].gsub(/^\//, "")
134
134
  l.error :err_uri_empty_segment if target.end_with? '/' or target.include? '//'
135
135
  l.error :err_uri_relative_path if target.include? '/../'
136
- relationship_file = zip_file.glob(target).first
137
- rel_type = rel[:type]
138
136
 
137
+ # necessary since it has been observed that rubyzip treats all zip entry names
138
+ # as ASCII-8BIT regardless if they really contain unicode chars. Without forcing
139
+ # the encoding we are unable to find the target within the zip if the target contains
140
+ # unicode chars.
141
+ zip_target = target
142
+ zip_target.force_encoding('ASCII-8BIT')
143
+ relationship_file = zip_file.glob(zip_target).first
144
+
145
+ rel_type = rel[:type]
139
146
  if relationship_file
140
147
  relationship_type = RELATIONSHIP_TYPES[rel_type]
141
148
  if relationship_type.nil?
@@ -161,7 +168,7 @@ class Document
161
168
  end
162
169
  end
163
170
  else
164
- l.error "Relationship Target file #{rel[:target]} not found", page: 11
171
+ l.error :rel_file_not_found, mf: "#{rel[:target]}"
165
172
  end
166
173
  end
167
174
  end
@@ -173,7 +180,7 @@ class Document
173
180
 
174
181
  return m
175
182
  rescue Zip::Error
176
- l.fatal_error 'File provided is not a valid ZIP archive', page: 9
183
+ l.fatal_error :not_a_zip
177
184
  end
178
185
  end
179
186
  rescue Log3mf::FatalError
@@ -214,4 +221,22 @@ class Document
214
221
  end
215
222
  end
216
223
 
224
+ private
225
+
226
+ def self.parse_uri(logger, uri)
227
+ begin
228
+ reserved_chars = /[\[\]]/
229
+
230
+ if uri =~ reserved_chars
231
+ logger.error :err_uri_bad
232
+ return nil
233
+ end
234
+
235
+ u = Addressable::URI.parse uri
236
+ rescue ArgumentError, Addressable::URI::InvalidURIError
237
+ logger.error :err_uri_bad
238
+ end
239
+ u
240
+ end
241
+
217
242
  end
@@ -1,41 +1,11 @@
1
- resource_contentype_invalid:
2
- msg: "Relationship target %{rt} resource has invalid contenttype %{bt}"
3
- page: 10
4
- err_uri_empty_segment:
5
- msg: 'No segment of a 3MF part name path may be empty'
6
- page: 13
7
- err_uri_hidden_file:
8
- msg: "Other than /_rels/.rels, no segment of a 3MF part name may start with the '.' character"
9
- page: 13
10
- err_uri_bad:
11
- msg: 'Path names must be valid Open Package Convention URIs or IRIs'
12
- page: 13
13
- err_uri_relative_path:
14
- msg: 'Part names must not include relative paths'
15
- page: 13
16
- model_resource_not_in_rels:
17
- msg: "Missing required resource: %{mr}. Resource referenced in model, but not in .rels relationship file"
18
- page: 10
19
- resource_3dmodel_orientation:
20
- msg: "Bad triangle orientation"
21
- page: 27
22
- resource_3dmodel_hole:
23
- msg: "Hole in model"
24
- page: 27
25
- resource_3dmodel_nonmanifold:
26
- msg: "Non-manifold edge in 3dmodel"
1
+ build_with_other_item:
2
+ msg: "build item with object of type other"
27
3
  page: 27
28
- 3d_model_invalid_xml:
29
- msg: "Model file invalid XML. Exception Start tag expected, '<' not found"
30
- page:
31
- 3d_payload_files:
32
- msg: "Relationship Target file /3D/Texture/texture2.texture not found"
33
- page: 11
34
4
  content_types_invalid_xml:
35
- msg: "[Content_Types].xml file is not valid XML. Start tag expected, '<' not found"
5
+ msg: "[Content_Types].xml file is not valid XML. %{e}"
36
6
  page: 15
37
7
  dot_rels_file_has_invalid_xml:
38
- msg: "Relationships (.rel) file is not a valid XML file: Start tag expected, '<' not found"
8
+ msg: "Relationships (.rel) file is not a valid XML file: %{e}"
39
9
  page: 4
40
10
  dot_rels_file_missing_relationships_element:
41
11
  msg: ".rels XML must have &lt;Relationships&gt; root element"
@@ -43,133 +13,176 @@ dot_rels_file_missing_relationships_element:
43
13
  dot_rels_file_no_relationship_element:
44
14
  msg: "No relationship elements found"
45
15
  page: 4
16
+ duplicate_content_extension_types:
17
+ msg: "Only one ContentType definition is allowed per extension"
18
+ page: 8
19
+ duplicate_content_override_types:
20
+ msg: "Only one override is allowed per part"
21
+ page: 8
22
+ empty_override_part_name:
23
+ msg: "Overrides can't have empty partname"
24
+ page: 8
25
+ err_uri_bad:
26
+ msg: 'Path names must be valid Open Package Convention URIs or IRIs'
27
+ page: 13
28
+ err_uri_empty_segment:
29
+ msg: 'No segment of a 3MF part name path may be empty'
30
+ page: 13
31
+ err_uri_hidden_file:
32
+ msg: "Other than /_rels/.rels, no segment of a 3MF part name may start with the '.' character"
33
+ page: 13
34
+ err_uri_relative_path:
35
+ msg: 'Part names must not include relative paths'
36
+ page: 13
37
+ has_base_materials_gradient:
38
+ msg: "Base materials form a gradient on one or more triangles. Interpolation of materials is not supported in the core spec."
39
+ page: 31
40
+ has_commas_for_floats:
41
+ msg: "numbers not formatted for the en-US locale"
42
+ page: 15
43
+ has_improper_base_color:
44
+ msg: "An sRGB color MUST be specified with a value of a 6 or 8 digit hexadecimal number"
45
+ page: 35
46
+ has_xml_space_attribute:
47
+ msg: "xml:space attribute is not allowed"
48
+ page: 16
46
49
  invalid_content_type:
47
- msg: "[Content_Types].xml is missing required ContentType \"application/vnd.openxmlformats-package.relationships+xml\""
48
- page: 10
49
- invalid_startpart_type:
50
- msg: "rels/.rels Relationship file is missing a required StartPart relationship to the primary 3D payload"
50
+ msg: "[Content_Types].xml is missing required ContentType \"%{mt}\""
51
51
  page: 10
52
+ invalid_image_content_type:
53
+ msg: "Invalid content type for %{extension}"
54
+ page: 22
55
+ invalid_metadata_name:
56
+ msg: "Metadata names must be prefixed with a valid namespace"
57
+ page: 21
58
+ invalid_metadata_under_defaultns:
59
+ msg: "Metadata without a namespace name must only contain allowed name values"
60
+ page: 21
61
+ invalid_model_unit:
62
+ msg: "Invalid unit value of '%{unit}' specified in model file."
63
+ page: 17
52
64
  invalid_startpart_target:
53
65
  msg: "Invalid StartPart target '%{target}'. The 3MF Document StartPart relationship MUST point to the 3D Model part that identifies the root of the 3D payload."
54
66
  page: 10
55
- unsupported_relationship_type:
56
- msg: "Validation of relationship type '%{type}' is not supported by this tool. The targeted part '%{target}' will not be validated."
57
- page: 10
58
- invalid_thumbnail_file:
59
- msg: "thumbnail file must be valid image file"
67
+ invalid_startpart_type:
68
+ msg: "rels/.rels Relationship file is missing a required StartPart relationship to the primary 3D payload"
60
69
  page: 10
70
+ invalid_texture_file_type:
71
+ msg: "Expected a png or jpeg texture but the texture was of type %{type}"
72
+ spec: :material
73
+ page: 16
61
74
  invalid_thumbnail_colorspace:
62
75
  msg: "CMYK JPEG images must not be used for the thumbnail"
63
76
  page: 36
77
+ invalid_thumbnail_file:
78
+ msg: "thumbnail file must be valid image file"
79
+ page: 10
64
80
  invalid_thumbnail_file_type:
65
81
  msg: "Expected a png or jpeg thumbnail but the thumbnail was of type %{type}"
66
82
  page: 36
67
- invalid_texture_file_type:
68
- msg: "Expected a png or jpeg texture but the texture was of type image/gif"
69
- page: 16
70
- missing_content_types:
71
- msg: "Missing required file: [Content_Types].xml"
72
- page: 4
83
+ invalid_vertex_index:
84
+ msg: "Triangle with a invalid vertex index"
85
+ page: 26
86
+ invalid_xml_core:
87
+ msg: "XML file doesn't pass validation with the XSD file"
88
+ page: 15
89
+ inward_facing_normal:
90
+ msg: "All trianges must be oriented with normals pointing away from interior"
91
+ page: 31
92
+ metadata_elements_with_same_name:
93
+ msg: "metadata elements must not share the same name"
94
+ page: 22
73
95
  missing_content_type:
74
96
  msg: "Unable to find an associated content type for part '%{part}' in [Content_Types].xml"
75
97
  page: 10
98
+ missing_content_types:
99
+ msg: "Missing required file: [Content_Types].xml"
100
+ page: 4
76
101
  missing_dot_rels_file:
77
102
  msg: "Missing required file _rels/.rels"
78
103
  page: 4
104
+ missing_extension_namespace_uri:
105
+ msg: "Required extension '%{ns}' MUST refer to namespace with URI"
106
+ page: 14
107
+ missing_model_children:
108
+ msg: "Model element must include resources and build as child elements"
109
+ page: 20
110
+ missing_object_pid:
111
+ msg: "Object with triangle color override missing object level pid"
112
+ page: 26
113
+ missing_object_reference:
114
+ msg: "3D objects not referenced by an item element"
115
+ page: 23
79
116
  missing_rels_folder:
80
117
  msg: "Missing required file _rels/.rels"
81
118
  page: 4
119
+ model_invalid_xml:
120
+ msg: "Model file invalid XML. Exception %{e}"
121
+ page:
122
+ model_resource_not_in_rels:
123
+ msg: "Missing required resource: %{mr}. Resource referenced in model, but not in .rels relationship file"
124
+ page: 10
125
+ multiple_print_tickets:
126
+ msg: "Only one print ticket allowed for any given model"
127
+ page: 13
128
+ multiple_relationships:
129
+ msg: "There MUST NOT be more than one relationship of a given relationship type from one part to a second part"
130
+ page: 10
131
+ non_distinct_indices:
132
+ msg: "The indices v1, v2 and v3 MUST be distinct."
133
+ page: 31
82
134
  non_unique_rel_id:
83
135
  msg: "The ID value '%{id}' appears more than once in '%{file}'. Within a rels file, all ID's must be unique"
84
136
  page: 8 #TODO:change this to page 23 of the OPC spec once logging supports linking to that spec
85
137
  spec: opc
86
- multiple_relationships:
87
- msg: "There MUST NOT be more than one relationship of a given relationship type from one part to a second part"
88
- page: 10
89
138
  not_a_zip:
90
139
  msg: "File provided is not a valid ZIP archive"
91
140
  page: 9
92
- zero_size_texture:
93
- msg: "Texture file must be valid image file"
94
- page: 16
95
- has_xml_space_attribute:
96
- msg: "xml:space attribute is not allowed"
97
- page: 16
98
- invalid_model_unit:
99
- msg: "Invalid unit value of '%{unit}' specified in model file."
100
- page: 17
101
- wrong_encoding:
102
- msg: "XML content must be UTF8 encoded"
103
- page: 15
104
- missing_object_pid:
105
- msg: "Object with triangle color override missing object level pid"
106
- page: 26
107
- missing_model_children:
108
- msg: "Model element must include resources and build as child elements"
109
- page: 20
141
+ not_enough_triangles:
142
+ msg: "Mesh has fewer than four triangles"
143
+ page: 30
110
144
  object_with_components_and_pid:
111
145
  msg: "object with components and pid or pindex"
112
146
  page: 26
113
- build_with_other_item:
114
- msg: "build item with object of type other"
147
+ rel_file_not_found:
148
+ msg: "Relationship Target file %{mf} not found"
149
+ page: 11
150
+ resource_3dmodel_hole:
151
+ msg: "Hole in model"
152
+ page: 27
153
+ resource_3dmodel_nonmanifold:
154
+ msg: "Non-manifold edge in 3dmodel"
155
+ page: 27
156
+ resource_3dmodel_orientation:
157
+ msg: "Bad triangle orientation"
115
158
  page: 27
159
+ resource_contentype_invalid:
160
+ msg: "Relationship target %{rt} resource has invalid contenttype %{bt}"
161
+ page: 10
116
162
  resource_id_collision:
117
163
  msg: "resources must be unique within the model"
118
164
  page: 22
119
165
  resource_pid_missing:
120
- msg: "A model resource referenced a property group id (pid) that is not present. Missing pid is: %{pid}"
166
+ msg: "A model resource referenced a property group id (pid) that is not present. Missing pid is: %{pid}"
121
167
  page: 20
122
- metadata_elements_with_same_name:
123
- msg: "metadata elements must not share the same name"
124
- page: 22
125
- multiple_print_tickets:
126
- msg: "Only one print ticket allowed for any given model"
127
- page: 13
168
+ schema_error:
169
+ msg: "Schema error found: %{e}"
170
+ thumbnail_image_type_mismatch:
171
+ msg: "Image not of declared type"
172
+ page: 36
128
173
  unknown_required_extension:
129
174
  msg: "Required extension not supported: %{ext}"
130
175
  page: 14
131
- missing_extension_namespace_uri:
132
- msg: "Required extension '%{ns}' MUST refer to namespace with URI"
133
- page: 14
134
- invalid_metadata_under_defaultns:
135
- msg: "Metadata without a namespace name must only contain allowed name values"
136
- page: 21
137
- invalid_metadata_name:
138
- msg: "Metadata names must be prefixed with a valid namespace"
139
- page: 21
140
- has_commas_for_floats:
141
- msg: "numbers not formatted for the en-US locale"
142
- page: 15
143
- invalid_xml_core:
144
- msg: "XML file doesn't pass validation with the XSD file"
176
+ unsupported_relationship_type:
177
+ msg: "Validation of relationship type '%{type}' is not supported by this tool. The targeted part '%{target}' will not be validated."
178
+ page: 10
179
+ wrong_encoding:
180
+ msg: "XML content must be UTF8 encoded"
145
181
  page: 15
146
- missing_object_reference:
147
- msg: "3D objects not referenced by an item element"
148
- page: 23
149
- non_distinct_indices:
150
- msg: "The indices v1, v2 and v3 MUST be distinct."
151
- page: 31
152
- has_improper_base_color:
153
- msg: "An sRGB color MUST be specified with a value of a 6 or 8 digit hexadecimal number"
154
- page: 35
155
- duplicate_content_extension_types:
156
- msg: "Only one ContentType definition is allowed per extension"
157
- page: 8
158
- invalid_image_content_type:
159
- msg: "Invalid content type for %{extension}"
160
- page: 22
161
- duplicate_content_override_types:
162
- msg: "Only one override is allowed per part"
163
- page: 8
164
- empty_override_part_name:
165
- msg: "Overrides can't have empty partname"
166
- page: 8
167
- not_enough_triangles:
168
- msg: "Mesh has fewer than four triangles"
169
- page: 30
170
- has_base_materials_gradient:
171
- msg: "Base materials form a gradient on one or more triangles. Interpolation of materials is not supported in the core spec."
172
- page: 31
173
- thumbnail_image_type_mismatch:
174
- msg: "Image not of declared type"
175
- page: 36
182
+ zero_size_texture:
183
+ msg: "Texture file must be valid image file"
184
+ spec: :material
185
+ page: 16
186
+ contains_xsi_namespace:
187
+ msg: "XML content must not use the xsi namespace as it is not defined in the XSD schema"
188
+ page: 15
@@ -70,8 +70,21 @@ class Log3mf
70
70
 
71
71
  def method_missing(name, *args, &block)
72
72
  if LOG_LEVELS.include? name.to_sym
73
- log(name.to_sym, *args)
74
- else+
73
+ if [:fatal_error, :error, :debug].include? name.to_sym
74
+ linenumber = caller_locations[0].to_s.split('/')[-1].split(':')[-2].to_s
75
+ filename = caller_locations[0].to_s.split('/')[-1].split(':')[0].to_s
76
+ options = {linenumber: linenumber, filename: filename}
77
+ # Mike: do not call error or fatal_error without an entry in errors.yml
78
+ raise "{fatal_}error called WITHOUT using error symbol from: #{filename}:#{linenumber}" if ( !(args[0].is_a? Symbol) && (name.to_sym != :debug) )
79
+
80
+ puts "***** Log3mf.#{name} called from #{filename}:#{linenumber} *****" if $DEBUG
81
+
82
+ options = options.merge(args[1]) if args[1]
83
+ log(name.to_sym, args[0], options)
84
+ else
85
+ log(name.to_sym, *args)
86
+ end
87
+ else
75
88
  super
76
89
  end
77
90
  end
@@ -80,11 +93,12 @@ class Log3mf
80
93
  error = @errormap.fetch(message.to_s) { {"msg" => message.to_s, "page" => nil} }
81
94
  options[:page] = error["page"] unless options[:page]
82
95
  options[:spec] = error["spec"] unless options[:spec]
83
- entry = { id: message,
84
- context: "#{@context_stack.join("/")}",
85
- severity: severity,
86
- message: interpolate(error["msg"], options) }
96
+ entry = {id: message,
97
+ context: "#{@context_stack.join("/")}",
98
+ severity: severity,
99
+ message: interpolate(error["msg"], options)}
87
100
  entry[:spec_ref] = spec_link(options[:spec], options[:page]) if (options && options[:page])
101
+ entry[:caller] = "#{options[:filename]}:#{options[:linenumber]}" if (options && options[:filename] && options[:linenumber])
88
102
  @log_list << entry
89
103
  raise FatalError if severity == :fatal_error
90
104
  end
@@ -1,14 +1,3 @@
1
- def find_child(node, child_name)
2
- node.children.each do |child|
3
- if child.name == child_name
4
- return child
5
- end
6
- end
7
-
8
- nil
9
- end
10
-
11
-
12
1
  class MeshAnalyzer
13
2
 
14
3
  def self.validate_object(object, includes_material)
@@ -27,6 +16,7 @@ class MeshAnalyzer
27
16
  meshs = object.css('mesh')
28
17
  meshs.each do |mesh|
29
18
 
19
+ num_vertices = mesh.css("vertex").count
30
20
  triangles = mesh.css("triangle")
31
21
  l.error :not_enough_triangles if triangles.count < 4
32
22
 
@@ -37,6 +27,8 @@ class MeshAnalyzer
37
27
  v2 = triangle.attributes["v2"].to_s.to_i
38
28
  v3 = triangle.attributes["v3"].to_s.to_i
39
29
 
30
+ l.error :invalid_vertex_index if [v1, v2, v3].select{|vertex| vertex >= num_vertices}.count > 0
31
+
40
32
  unless includes_material
41
33
  l.context "validating property overrides" do |l|
42
34
  property_overrides = []
@@ -0,0 +1,219 @@
1
+ class MeshNormalAnalyzer
2
+
3
+ def initialize(mesh)
4
+ @vertices = []
5
+ @intersections = []
6
+
7
+ vertices_node = mesh.css("vertices")
8
+ vertices_node.children.each do |vertex_node|
9
+ if vertex_node.attributes.count > 0
10
+ x = vertex_node.attributes['x'].to_s.to_f
11
+ y = vertex_node.attributes['y'].to_s.to_f
12
+ z = vertex_node.attributes['z'].to_s.to_f
13
+ @vertices << [x, y, z]
14
+ end
15
+ end
16
+
17
+ @triangles = []
18
+ triangles_node = mesh.css("triangles")
19
+ triangles_node.children.each do |triangle_node|
20
+ if triangle_node.attributes.count > 0
21
+ v1 = triangle_node.attributes['v1'].to_s.to_i
22
+ v2 = triangle_node.attributes['v2'].to_s.to_i
23
+ v3 = triangle_node.attributes['v3'].to_s.to_i
24
+ @triangles << [v1, v2, v3]
25
+ end
26
+ end
27
+ end
28
+
29
+ def found_inward_triangle
30
+ # Trace a ray toward the center of the vertex points. This will hopefully
31
+ # maximize our chances of hitting the object's trianges on the first try.
32
+ center = point_cloud_center(@vertices)
33
+
34
+ @point = [0.0, 0.0, 0.0]
35
+ @direction = vector_to(@point, center)
36
+
37
+ # Make sure that we have a reasonably sized direction.
38
+ # Might end up with a zero length vector if the center is also
39
+ # at the origin.
40
+ if magnitude(@direction) < 0.1
41
+ @direction = [0.57, 0.57, 0.57]
42
+ end
43
+
44
+ # make the direction a unit vector just to make the
45
+ # debug info easier to understand
46
+ @direction = normalize(@direction)
47
+
48
+ attempts = 0
49
+ begin
50
+ # Get all of the intersections from the ray and put them in order of distance.
51
+ # The triangle we hit that's farthest from the start of the ray should always be
52
+ # a triangle that points away from us (otherwise we would hit a triangle even
53
+ # further away, assuming the mesh is closed).
54
+ #
55
+ # One special case is when the set of triangles we hit at that distance is greater
56
+ # than one. In that case we might have hit a "corner" of the model and so we don't
57
+ # know which of the two (or more) points away from us. In that case, cast a random
58
+ # ray from the center of the object and try again.
59
+
60
+ @triangles.each do |tri|
61
+ v1 = @vertices[tri[0]]
62
+ v2 = @vertices[tri[1]]
63
+ v3 = @vertices[tri[2]]
64
+
65
+ process_triangle(@point, @direction, [v1, v2, v3])
66
+ end
67
+
68
+ if @intersections.count > 0
69
+ # Sort the intersections so we can find the hits that are furthest away.
70
+ @intersections.sort! {|left, right| left[0] <=> right[0]}
71
+
72
+ max_distance = @intersections.last[0]
73
+ furthest_hits = @intersections.select{|hit| (hit[0]-max_distance).abs < 0.0001}
74
+
75
+ # Print out the hits
76
+ # furthest_hits.each {|hit| puts hit[1].to_s}
77
+
78
+ found_good_hit = furthest_hits.count == 1
79
+ end
80
+
81
+ if found_good_hit
82
+ outside_triangle = furthest_hits.last[2]
83
+ else
84
+ @intersections = []
85
+ attempts = attempts + 1
86
+
87
+ target = [Random.rand(10)/10.0, Random.rand(10)/10.0, Random.rand(10)/10.0]
88
+ @point = center
89
+ @direction = normalize(vector_to(@point, target))
90
+ end
91
+ end until found_good_hit || attempts >= 10
92
+
93
+ # return true if we hit a triangle with an inward pointing normal
94
+ # (according to counter-clockwise normal orientation)
95
+ found_good_hit && !compare_normals(outside_triangle, @direction)
96
+ end
97
+
98
+ def compare_normals(triangle, hit_direction)
99
+ oriented_normal = cross_product(
100
+ vector_to(triangle[0], triangle[1]),
101
+ vector_to(triangle[1], triangle[2]))
102
+
103
+ angle = angle_between(oriented_normal, hit_direction)
104
+
105
+ angle < Math::PI / 2.0
106
+ end
107
+
108
+ def process_triangle(point, direction, triangle)
109
+ found_intersection, t = intersect(point, direction, triangle)
110
+
111
+ if t > 0
112
+ intersection = []
113
+ intersection[0] = point[0] + t * direction[0]
114
+ intersection[1] = point[1] + t * direction[1]
115
+ intersection[2] = point[2] + t * direction[2]
116
+
117
+ @intersections << [t, intersection, triangle]
118
+ end
119
+ end
120
+
121
+ def intersect(point, direction, triangle)
122
+ v0 = triangle[0]
123
+ v1 = triangle[1]
124
+ v2 = triangle[2]
125
+
126
+ return [false, 0] if v0.nil? || v1.nil? || v2.nil?
127
+
128
+ e1 = vector_to(v0, v1)
129
+ e2 = vector_to(v0, v2)
130
+
131
+ h = cross_product(direction, e2)
132
+ a = dot_product(e1, h)
133
+
134
+ if a.abs < 0.00001
135
+ return false, 0
136
+ end
137
+
138
+ f = 1.0/a
139
+ s = vector_to(v0, point)
140
+ u = f * dot_product(s, h)
141
+
142
+ if u < 0.0 || u > 1.0
143
+ return false, 0
144
+ end
145
+
146
+ q = cross_product(s, e1)
147
+ v = f * dot_product(direction, q)
148
+
149
+ if v < 0.0 || u + v > 1.0
150
+ return false, 0
151
+ end
152
+
153
+ t = f * dot_product(e2, q)
154
+ [t > 0, t]
155
+ end
156
+ end
157
+
158
+ # Various utility functions
159
+
160
+ def cross_product(a, b)
161
+ result = [0, 0, 0]
162
+ result[0] = a[1]*b[2] - a[2]*b[1]
163
+ result[1] = a[2]*b[0] - a[0]*b[2]
164
+ result[2] = a[0]*b[1] - a[1]*b[0]
165
+
166
+ result
167
+ end
168
+
169
+ def dot_product(a, b)
170
+ a[0]*b[0] + a[1]*b[1] + a[2]*b[2]
171
+ end
172
+
173
+ def vector_to(a, b)
174
+ [b[0] - a[0], b[1] - a[1], b[2] - a[2]]
175
+ end
176
+
177
+ def magnitude(a)
178
+ Math.sqrt(a[0]*a[0] + a[1]*a[1] + a[2]*a[2])
179
+ end
180
+
181
+ def equal(a, b)
182
+ (a[0] - b[0]).abs < 0.0001 && (a[1] - b[1]).abs < 0.0001 && (a[2] - b[2]).abs < 0.0001
183
+ end
184
+
185
+ def angle_between(a, b)
186
+ cos_theta = dot_product(a, b) / (magnitude(a) * magnitude(b))
187
+ Math.acos(cos_theta)
188
+ end
189
+
190
+ def normalize(a)
191
+ length = magnitude(a)
192
+ [a[0]/length, a[1]/length, a[2]/length]
193
+ end
194
+
195
+ def point_cloud_center(vertices)
196
+ if vertices.count < 1
197
+ return [0, 0, 0]
198
+ end
199
+
200
+ vertex = vertices[0]
201
+ min_x = max_x = vertex[0]
202
+ min_y = max_y = vertex[1]
203
+ min_z = max_z = vertex[2]
204
+
205
+ vertices.each do |vertex|
206
+ x = vertex[0]
207
+ y = vertex[1]
208
+ z = vertex[2]
209
+
210
+ min_x = x if x < min_x
211
+ max_x = x if x > max_x
212
+ min_y = y if y < min_y
213
+ max_y = y if y > max_y
214
+ min_z = z if z < min_z
215
+ max_z = z if z > max_z
216
+ end
217
+
218
+ [(min_x + max_x) / 2.0, (min_y + max_y) / 2.0, (min_z + max_z) / 2.0]
219
+ end
@@ -19,7 +19,7 @@ class Model3mf
19
19
  begin
20
20
  model_doc = XmlVal.validate_parse(zip_entry, SchemaFiles::SchemaTemplate)
21
21
  rescue Nokogiri::XML::SyntaxError => e
22
- l.fatal_error "Model file invalid XML. Exception #{e}"
22
+ l.fatal_error :model_invalid_xml, e: e
23
23
  end
24
24
 
25
25
  l.context "verifying requiredextensions" do |l|
@@ -62,24 +62,10 @@ class Model3mf
62
62
  }
63
63
  end
64
64
 
65
- l.context "verifying StartPart relationship points to the root 3D Model" do |l|
66
- #Find the root 3D model which is pointed to by the start part
67
- root_rels = document.relationships['_rels/.rels']
68
- unless root_rels.nil?
69
- start_part_rel = root_rels.select { |rel| rel[:type] == Document::MODEL_TYPE }.first
70
- unless start_part_rel.nil? || start_part_rel[:target] != '/' + zip_entry.name
71
- #Verify that the model is a valid root 3D model by checking if it has at least one object
72
- l.fatal_error :invalid_startpart_target, :target => start_part_rel[:target] if model_doc.css("//model//resources//object").size == 0
73
- end
74
- else
75
- l.fatal_error :missing_dot_rels_file
76
- end
77
- end
78
-
79
65
  end
80
66
 
81
67
  l.context 'verifying resources' do |l|
82
- resources = find_child(model_doc.root, 'resources')
68
+ resources = model_doc.root.css("resources")
83
69
  if resources
84
70
  ids = resources.children.map { |child| child.attributes['id'].to_s if child.attributes['id'] }
85
71
  l.error :resource_id_collision if ids.uniq.size != ids.size
@@ -113,8 +99,19 @@ class Model3mf
113
99
  end
114
100
  end
115
101
  end
102
+
116
103
  includes_material = model_doc.namespaces.values.include?(MATERIAL_EXTENSION)
117
104
  MeshAnalyzer.validate(model_doc, includes_material)
105
+
106
+ l.context "verifying triangle normal" do |l|
107
+ model_doc.css('model/resources/object').select { |object| ['model', 'solidsupport', ''].include?(object.attributes['type'].to_s) }.each do |object|
108
+ meshes = object.css('mesh')
109
+ meshes.each do |mesh|
110
+ processor = MeshNormalAnalyzer.new(mesh)
111
+ l.error :inward_facing_normal if processor.found_inward_triangle
112
+ end
113
+ end
114
+ end
118
115
  end
119
116
  model_doc
120
117
  end
@@ -35,14 +35,14 @@ class Relationships
35
35
  end
36
36
  end
37
37
  else
38
- l.error "No relationship elements found", page: 4
38
+ l.error :dot_rels_file_no_relationship_element
39
39
  end
40
40
  else
41
- l.error ".rels XML must have &lt;Relationships&gt; root element", page: 4
41
+ l.error :dot_rels_file_missing_relationships_element
42
42
  end
43
43
 
44
44
  rescue Nokogiri::XML::SyntaxError => e
45
- l.error "Relationships (.rel) file is not a valid XML file: #{e.message}", page: 4
45
+ l.error :dot_rels_file_has_invalid_xml, e: "#{e.message}"
46
46
  end
47
47
  end
48
48
  relationships
@@ -11,9 +11,9 @@ class Texture3mf
11
11
  stream = relationship_file.get_input_stream
12
12
  img_type = MimeMagic.by_magic(stream)
13
13
  Log3mf.context "Texture3mf" do |l|
14
- l.fatal_error "Texture file must be valid image file", spec: :material, page: 16 unless img_type
14
+ l.fatal_error :zero_size_texture unless img_type
15
15
  l.debug "texture is of type: #{img_type}"
16
- l.error "Expected a png or jpeg texture but the texture was of type #{img_type}", spec: :material, page: 16 unless ['image/png', 'image/jpeg'].include? img_type.type
16
+ l.error(:invalid_texture_file_type, type: img_type) unless ['image/png', 'image/jpeg'].include? img_type.type
17
17
  end
18
18
  t
19
19
  end
@@ -1,3 +1,3 @@
1
1
  module Ruby3mf
2
- VERSION = "0.2.4"
2
+ VERSION = "0.2.5"
3
3
  end
@@ -17,6 +17,7 @@ class XmlVal
17
17
  l.error :dtd_not_allowed if dtd_exists?(file)
18
18
  l.error :has_commas_for_floats if bad_floating_numbers?(document)
19
19
  l.warning :missing_object_reference if objects_not_referenced?(document)
20
+ l.error :contains_xsi_namespace if contains_xsi_namespace?(document)
20
21
 
21
22
  if schema_filename
22
23
  Log3mf.context "validating core schema" do |l|
@@ -29,7 +30,7 @@ class XmlVal
29
30
  if error_involves_colorvalue?(error)
30
31
  l.error :has_improper_base_color
31
32
  else
32
- l.error error
33
+ l.error :schema_error, e: error
33
34
  end
34
35
  end
35
36
  end
@@ -66,4 +67,8 @@ class XmlVal
66
67
  def self.error_involves_colorvalue?(error)
67
68
  error.to_s.include?("ST_ColorValue") || error.to_s.include?("displaycolor")
68
69
  end
70
+
71
+ def self.contains_xsi_namespace?(document)
72
+ document.namespaces.has_value?('http://www.w3.org/2001/XMLSchema-instance')
73
+ end
69
74
  end
data/lib/ruby3mf.rb CHANGED
@@ -11,6 +11,7 @@ require_relative "ruby3mf/texture3mf"
11
11
  require_relative "ruby3mf/xml_val"
12
12
  require_relative "ruby3mf/edge_list"
13
13
  require_relative "ruby3mf/mesh_analyzer"
14
+ require_relative "ruby3mf/mesh_normal_analyzer"
14
15
 
15
16
  require 'zip'
16
17
  require 'nokogiri'
@@ -18,6 +19,7 @@ require 'json'
18
19
  require 'mimemagic'
19
20
  require 'uri'
20
21
  require 'yaml'
22
+ require "addressable/uri"
21
23
 
22
24
  module Ruby3mf
23
25
  # Your code goes here...
data/ruby3mf.gemspec CHANGED
@@ -28,4 +28,5 @@ Gem::Specification.new do |spec|
28
28
  spec.add_runtime_dependency 'nokogiri', '~>1.6.8'
29
29
  spec.add_runtime_dependency 'mimemagic'
30
30
  spec.add_runtime_dependency 'mini_magick', '~> 4.6'
31
+ spec.add_runtime_dependency 'addressable', '~> 2.5'
31
32
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby3mf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Whitmarsh, Jeff Porter, and William Hertling
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-01-24 00:00:00.000000000 Z
11
+ date: 2017-01-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -122,6 +122,20 @@ dependencies:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
124
  version: '4.6'
125
+ - !ruby/object:Gem::Dependency
126
+ name: addressable
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '2.5'
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '2.5'
125
139
  description: Read, write and validate 3MF files with Ruby easily.
126
140
  email:
127
141
  - mwhit@hp.com
@@ -139,7 +153,6 @@ files:
139
153
  - CODE_OF_CONDUCT.md
140
154
  - Gemfile
141
155
  - LICENSE.txt
142
- - PO_102_03.3mf
143
156
  - README.md
144
157
  - Rakefile
145
158
  - bin/batch.rb
@@ -163,6 +176,7 @@ files:
163
176
  - lib/ruby3mf/interpolation.rb
164
177
  - lib/ruby3mf/log3mf.rb
165
178
  - lib/ruby3mf/mesh_analyzer.rb
179
+ - lib/ruby3mf/mesh_normal_analyzer.rb
166
180
  - lib/ruby3mf/model3mf.rb
167
181
  - lib/ruby3mf/relationships.rb
168
182
  - lib/ruby3mf/schema_files.rb
data/PO_102_03.3mf DELETED
Binary file