Index: cpp/frams/model/model.cpp
===================================================================
--- cpp/frams/model/model.cpp	(revision 458)
+++ cpp/frams/model/model.cpp	(revision 495)
@@ -326,10 +326,7 @@
 		}
 	mh.reset();
-	if (singleStepBuild(line,autobuildmaps ? (&frommap) : 0)==-1)
+	if (singleStepBuild(line,lnum,autobuildmaps ? (&frommap) : 0) == -1)
 		{
 		buildstatus=invalid;
-		logPrintf("Model","build",LOG_ERROR,
-			 geno.getName().len()?"illegal f0 code at line %d (%s)":"illegal f0 code at line %d",
-			 lnum,geno.getName().c_str());
 		f0errorposition=lastpos;
 		if (convmap) delete convmap;
@@ -407,5 +404,5 @@
 modelparam.select(this);
 modelparam.save2(mod_props,handle_defaults ? &defaultmodel : NULL,true,!handle_defaults);
-if (mod_props.len()>0) //are there any non-default values?
+if (mod_props.len()>1) //are there any non-default values? ("\n" is empty)
 	{
 	gen+="m:";
@@ -513,5 +510,5 @@
 {
 if (buildstatus!=building)
-	logPrintf("Model","close",LOG_WARN,"unexpected close() - no open()");
+	logPrintf("Model","close",LOG_WARN,"Unexpected close() - no open()");
 if (internalcheck(1)>0)
 	{
@@ -557,6 +554,28 @@
 }
 
-int Model::singleStepBuild(const SString &line,const MultiRange* srcrange)
-{
+int Model::singleStepBuild(const SString &singleline,const MultiRange* srcrange)
+{
+return singleStepBuild(singleline,0,srcrange);
+}
+
+int Model::singleStepBuild(const SString &singleline,int line_num,const MultiRange* srcrange)
+{
+SString error_message;
+int result=singleStepBuildNoLog(singleline,error_message,srcrange);
+if (result<0)
+	{
+	if (error_message.len()==0) // generic error when no detailed message is available
+		error_message="Invalid f0 code";
+	if (line_num>0)
+		error_message+=SString::sprintf(", line #%d",line_num);
+	error_message+=nameForErrors();
+	logPrintf("Model","build",LOG_ERROR,"%s",error_message.c_str());
+	}
+return result;
+}
+
+int Model::singleStepBuildNoLog(const SString &line,SString& error_message,const MultiRange* srcrange)
+{
+error_message=SString::empty();
 int pos=0; const char*t=line.c_str();
 for (;*t;t++,pos++)
@@ -570,5 +589,5 @@
 	partparam.select(p);
 	pos+=2;
-	if (partparam.load2(line,pos) & ParamInterface::LOAD2_PARSE_FAILED) {delete p; return -1;}
+	if (partparam.load2(line,pos) & ParamInterface::LOAD2_PARSE_FAILED) {delete p; error_message="Invalid 'p:'"; return -1;}
 	p->o.rotate(p->rot);
 	parts+=p;
@@ -582,5 +601,5 @@
 	modelparam.select(this);
 	pos+=2;
-	if (modelparam.load2(line,pos) & ParamInterface::LOAD2_PARSE_FAILED) return -1;
+	if (modelparam.load2(line,pos) & ParamInterface::LOAD2_PARSE_FAILED) { error_message="Invalid 'm:'"; return -1;}
 	return 0;
 	}
@@ -592,7 +611,8 @@
 	pos+=2;
 	j->owner=this;
-	if (jointparam.load2(line,pos) & ParamInterface::LOAD2_PARSE_FAILED) {delete j; return -1;}
-	if ((j->p1_refno>=0)&&(j->p1_refno<getPartCount())&&
-	   (j->p2_refno>=0)&&(j->p2_refno<getPartCount()))
+	if (jointparam.load2(line,pos) & ParamInterface::LOAD2_PARSE_FAILED) {delete j; error_message="Invalid 'j:'"; return -1;}
+	bool p1_ok=false, p2_ok=false;
+	if ((p1_ok=((j->p1_refno>=0)&&(j->p1_refno<getPartCount())))&&
+	    (p2_ok=((j->p2_refno>=0)&&(j->p2_refno<getPartCount()))))
 		{
 		addJoint(j);
@@ -609,6 +629,5 @@
 		{
 		delete j;
-		logPrintf("Model","build",LOG_ERROR,
-			 "invalid part reference for joint #%d",getJointCount()-1);
+		error_message=SString::sprintf("Invalid reference to Part #%d",p1_ok?j->p1_refno:j->p2_refno);
 		return -1;
 		}
@@ -620,5 +639,5 @@
 	neuroparam.select(nu);
 	pos+=2;
-	if (neuroparam.load2(line,pos) & ParamInterface::LOAD2_PARSE_FAILED) {delete nu; return -1;}
+	if (neuroparam.load2(line,pos) & ParamInterface::LOAD2_PARSE_FAILED) {delete nu; error_message="Invalid 'n:'"; return -1;}
 #ifdef MODEL_V1_COMPATIBLE
 	if (nu->neuro_refno>=0) // parent specified...
@@ -684,6 +703,8 @@
 	ncparam.select(&c);
 	pos+=2;
-	if (ncparam.load2(line,pos) & ParamInterface::LOAD2_PARSE_FAILED) return -1;
-	if ((c.n1_refno>=0)&&(c.n1_refno<getNeuroCount())&&(c.n2_refno>=0)&&(c.n2_refno<getNeuroCount()))
+	if (ncparam.load2(line,pos) & ParamInterface::LOAD2_PARSE_FAILED) { error_message="Invalid 'c:'"; return -1; }
+	bool n1_ok=false,n2_ok=false;
+	if ((n1_ok=((c.n1_refno>=0)&&(c.n1_refno<getNeuroCount())))
+	    && (n2_ok=((c.n2_refno>=0)&&(c.n2_refno<getNeuroCount()))))
 		{
 		Neuro *na=getNeuro(c.n1_refno);
@@ -694,6 +715,5 @@
 		return 0;
 		}
-	logPrintf("Model","build",LOG_ERROR,
-		 "invalid neuron connection #%d <- #%d",c.n1_refno,c.n2_refno);
+	error_message=SString::sprintf("Invalid reference to Neuro #%d",n1_ok ? c.n2_refno : c.n1_refno);
 	return -1;
 	}
@@ -783,6 +803,6 @@
 				{
 				logPrintf("Model","internalCheck",LOG_ERROR,
-					 "illegal N-N connection #%d (reference to #%d) (%s)",
-					 i,n->conn_refno,(const char*)geno.getName());
+					 "illegal N-N connection #%d (reference to #%d)%s",
+					  i,n->conn_refno,nameForErrors().c_str());
 				return 0;
 				}
@@ -853,8 +873,8 @@
 if (var -> field < getMin ## template () . field) \
 	{ var->field= getMin ## template () . field; \
-	logPrintf("Model","internalCheck",LOG_WARN,# field " too small in " # template "#%d (adjusted)",i);} \
+	logPrintf("Model","internalCheck",LOG_WARN,# field " too small in " # template " #%d (adjusted)",i);} \
 else if (var -> field > getMax ## template () . field) \
 	{ var->field= getMax ## template ()  . field; \
-	logPrintf("Model","internalCheck",LOG_WARN,# field " too big in " # template "#%d (adjusted)",i);}
+	logPrintf("Model","internalCheck",LOG_WARN,# field " too big in " # template " #%d (adjusted)",i);}
 
 #define LINKFLAG 0x8000000
@@ -866,4 +886,11 @@
 for (i=0;i<getJointCount();i++) getJoint(i)->refno=i;
 for (i=0;i<getNeuroCount();i++) getNeuro(i)->refno=i;
+}
+
+SString Model::nameForErrors() const
+{
+if (geno.getName().len()>0)
+	return SString::sprintf(" in '%s'",geno.getName().c_str());
+return SString::empty();
 }
 
@@ -912,5 +939,5 @@
 			{
 			shape=SHAPE_ILLEGAL;
-			logPrintf("Model","internalCheck",LOG_WARN,"Inconsistent part shapes (mixed old and new shapes)");
+			logPrintf("Model","internalCheck",LOG_WARN,"Inconsistent part shapes (mixed old and new shapes)%s",nameForErrors().c_str());
 			}
 		}
@@ -942,6 +969,6 @@
 				ret=0;
 				logPrintf("Model","internalCheck",LOG_ERROR,
-					 "delta joint cycle detected at joint#%d (%s)",
-					 i,geno.getName().c_str());
+					 "Delta joint cycle detected at Joint #%d%s",
+					 i,nameForErrors().c_str());
 				}
 			j->resetDeltaMarkers();
@@ -978,12 +1005,10 @@
 			{
 			ret=0;
-			logPrintf("Model","internalCheck",LOG_ERROR,"delta too big in joint #%d (%s)",
-				 i,geno.getName().c_str());
+			logPrintf("Model","internalCheck",LOG_ERROR,"Delta too big in Joint #%d%s",i,nameForErrors().c_str());
 			}
 			else if (j->d()<getMinJoint().d.x)
 			{
 			ret=0;
-			logPrintf("Model","internalCheck",LOG_ERROR,"delta too small in joint #%d (%s)",
-				 i,geno.getName().c_str());
+			logPrintf("Model","internalCheck",LOG_ERROR,"delta too small in Joint #%d%s",i,nameForErrors().c_str());
 			}
 				}
@@ -992,6 +1017,5 @@
 	else
 		{
-		logPrintf("Model","internalCheck",LOG_ERROR,"illegal part references in joint #%d (%s)",
-			 i,geno.getName().c_str());
+		logPrintf("Model","internalCheck",LOG_ERROR,"Illegal part references in Joint #%d%s",i,nameForErrors().c_str());
 		ret=0;
 		}
@@ -1001,5 +1025,5 @@
 			{
 			shape=SHAPE_ILLEGAL;
-			logPrintf("Model","internalCheck",LOG_WARN,"Inconsistent joint shapes (mixed old and new shapes)");
+			logPrintf("Model","internalCheck",LOG_WARN,"Inconsistent joint shapes (mixed old and new shapes)%s",nameForErrors().c_str());
 			}
 		}
@@ -1071,6 +1095,5 @@
 		if (!(p->flags&LINKFLAG))
 			{
-			logPrintf("Model","internalCheck",LOG_ERROR,"not all parts connected (eg.#0-#%d) (%s)",
-				 i,geno.getName().c_str());
+			logPrintf("Model","internalCheck",LOG_ERROR,"Not all parts connected (eg. Part #0 and Part #%d)%s",i,nameForErrors().c_str());
 			ret=0;
 			break;
@@ -1084,6 +1107,5 @@
 	if (j->p1_refno==j->p2_refno)
 		{
-		logPrintf("Model","internalCheck",LOG_ERROR,"illegal self connection, joint #%d (%s)",
-			 i,geno.getName().c_str());
+		logPrintf("Model","internalCheck",LOG_ERROR,"Illegal self connection, Joint #%d%s",i,nameForErrors().c_str());
 		ret=0;
 		break;
@@ -1095,6 +1117,5 @@
 		    || ((j->p1_refno==j2->p2_refno)&&(j->p2_refno==j2->p1_refno)))
 			{
-			logPrintf("Model","internalCheck",LOG_ERROR,"illegal duplicate joints #%d and #%d (%s)",
-				 i,k,geno.getName().c_str());
+			logPrintf("Model","internalCheck",LOG_ERROR,"Illegal duplicate Joint #%d and Joint #%d%s",i,k,nameForErrors().c_str());
 			ret=0;
 			break;
@@ -1125,5 +1146,5 @@
 {
 if (buildstatus==building)
-	logPrintf("Model","getGeno",LOG_WARN,"model was not completed - missing close()");
+	logPrintf("Model","getGeno",LOG_WARN,"Model was not completed - missing close()");
 if (buildstatus!=valid)
 	return Geno("",'0',"","invalid");
